summaryrefslogtreecommitdiff
path: root/utilities
diff options
context:
space:
mode:
authorBen Pfaff <blp@nicira.com>2009-07-08 13:19:16 -0700
committerBen Pfaff <blp@nicira.com>2009-07-08 13:19:16 -0700
commit064af42167bf4fc9aaea2702d80ce08074b889c0 (patch)
treeefd15a6dc2402eeec273bb34db3b2445687589e5 /utilities
downloadopenvswitch-064af42167bf4fc9aaea2702d80ce08074b889c0.tar.gz
Import from old repository commit 61ef2b42a9c4ba8e1600f15bb0236765edc2ad45.v0.90.0
Diffstat (limited to 'utilities')
-rw-r--r--utilities/.gitignore22
-rw-r--r--utilities/automake.mk74
-rw-r--r--utilities/nlmon.c90
-rw-r--r--utilities/ovs-appctl.8.in166
-rw-r--r--utilities/ovs-appctl.c221
-rw-r--r--utilities/ovs-cfg-mod.8.in101
-rw-r--r--utilities/ovs-cfg-mod.c239
-rw-r--r--utilities/ovs-controller.8.in132
-rw-r--r--utilities/ovs-controller.c323
-rw-r--r--utilities/ovs-discover.8.in118
-rw-r--r--utilities/ovs-discover.c405
-rw-r--r--utilities/ovs-dpctl.8.in166
-rw-r--r--utilities/ovs-dpctl.c552
-rw-r--r--utilities/ovs-kill.8.in60
-rw-r--r--utilities/ovs-kill.c210
-rwxr-xr-xutilities/ovs-monitor128
-rw-r--r--utilities/ovs-ofctl.8.in489
-rw-r--r--utilities/ovs-ofctl.c1278
-rwxr-xr-xutilities/ovs-parse-leaks.in285
-rwxr-xr-xutilities/ovs-pki-cgi.in41
-rw-r--r--utilities/ovs-pki.8.in323
-rwxr-xr-xutilities/ovs-pki.in582
-rw-r--r--utilities/ovs-wdt.c263
23 files changed, 6268 insertions, 0 deletions
diff --git a/utilities/.gitignore b/utilities/.gitignore
new file mode 100644
index 000000000..32a7f2eb7
--- /dev/null
+++ b/utilities/.gitignore
@@ -0,0 +1,22 @@
+/Makefile
+/Makefile.in
+/nlmon
+/ovs-appctl
+/ovs-appctl.8
+/ovs-cfg-mod
+/ovs-cfg-mod.8
+/ovs-controller
+/ovs-controller.8
+/ovs-discover
+/ovs-discover.8
+/ovs-dpctl
+/ovs-dpctl.8
+/ovs-kill
+/ovs-kill.8
+/ovs-ofctl
+/ovs-ofctl.8
+/ovs-parse-leaks
+/ovs-pki
+/ovs-pki-cgi
+/ovs-pki.8
+/ovs-wdt
diff --git a/utilities/automake.mk b/utilities/automake.mk
new file mode 100644
index 000000000..97b827acc
--- /dev/null
+++ b/utilities/automake.mk
@@ -0,0 +1,74 @@
+bin_PROGRAMS += \
+ utilities/ovs-appctl \
+ utilities/ovs-cfg-mod \
+ utilities/ovs-controller \
+ utilities/ovs-discover \
+ utilities/ovs-dpctl \
+ utilities/ovs-kill \
+ utilities/ovs-ofctl \
+ utilities/ovs-wdt
+noinst_PROGRAMS += utilities/nlmon
+bin_SCRIPTS += utilities/ovs-pki
+noinst_SCRIPTS += utilities/ovs-pki-cgi utilities/ovs-parse-leaks
+dist_sbin_SCRIPTS += utilities/ovs-monitor
+
+EXTRA_DIST += \
+ utilities/ovs-appctl.8.in \
+ utilities/ovs-cfg-mod.8.in \
+ utilities/ovs-controller.8.in \
+ utilities/ovs-discover.8.in \
+ utilities/ovs-dpctl.8.in \
+ utilities/ovs-kill.8.in \
+ utilities/ovs-ofctl.8.in \
+ utilities/ovs-parse-leaks.in \
+ utilities/ovs-pki-cgi.in \
+ utilities/ovs-pki.8.in \
+ utilities/ovs-pki.in
+DISTCLEANFILES += \
+ utilities/ovs-appctl.8 \
+ utilities/ovs-cfg-mod.8 \
+ utilities/ovs-controller.8 \
+ utilities/ovs-discover.8 \
+ utilities/ovs-dpctl.8 \
+ utilities/ovs-kill.8 \
+ utilities/ovs-ofctl.8 \
+ utilities/ovs-parse-leaks \
+ utilities/ovs-pki \
+ utilities/ovs-pki.8 \
+ utilities/ovs-pki-cgi
+
+man_MANS += \
+ utilities/ovs-appctl.8 \
+ utilities/ovs-cfg-mod.8 \
+ utilities/ovs-controller.8 \
+ utilities/ovs-discover.8 \
+ utilities/ovs-dpctl.8 \
+ utilities/ovs-kill.8 \
+ utilities/ovs-ofctl.8 \
+ utilities/ovs-pki.8
+
+utilities_ovs_appctl_SOURCES = utilities/ovs-appctl.c
+utilities_ovs_appctl_LDADD = lib/libopenvswitch.a
+
+utilities_ovs_cfg_mod_SOURCES = utilities/ovs-cfg-mod.c
+utilities_ovs_cfg_mod_LDADD = lib/libopenvswitch.a
+
+utilities_ovs_controller_SOURCES = utilities/ovs-controller.c
+utilities_ovs_controller_LDADD = lib/libopenvswitch.a $(FAULT_LIBS) $(SSL_LIBS)
+
+utilities_ovs_discover_SOURCES = utilities/ovs-discover.c
+utilities_ovs_discover_LDADD = lib/libopenvswitch.a
+
+utilities_ovs_dpctl_SOURCES = utilities/ovs-dpctl.c
+utilities_ovs_dpctl_LDADD = lib/libopenvswitch.a $(FAULT_LIBS)
+
+utilities_ovs_kill_SOURCES = utilities/ovs-kill.c
+utilities_ovs_kill_LDADD = lib/libopenvswitch.a
+
+utilities_ovs_ofctl_SOURCES = utilities/ovs-ofctl.c
+utilities_ovs_ofctl_LDADD = lib/libopenvswitch.a $(FAULT_LIBS) $(SSL_LIBS)
+
+utilities_ovs_wdt_SOURCES = utilities/ovs-wdt.c
+
+utilities_nlmon_SOURCES = utilities/nlmon.c
+utilities_nlmon_LDADD = lib/libopenvswitch.a
diff --git a/utilities/nlmon.c b/utilities/nlmon.c
new file mode 100644
index 000000000..eb1be60a7
--- /dev/null
+++ b/utilities/nlmon.c
@@ -0,0 +1,90 @@
+#include <config.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <stddef.h>
+#include <linux/rtnetlink.h>
+#include "netlink.h"
+#include "ofpbuf.h"
+#include "poll-loop.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static const struct nl_policy rtnlgrp_link_policy[] = {
+ [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
+ [IFLA_MASTER] = { .type = NL_A_U32, .optional = true },
+};
+
+int
+main(int argc UNUSED, char *argv[])
+{
+ struct nl_sock *sock;
+ int error;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ vlog_set_levels(VLM_ANY_MODULE, VLF_ANY_FACILITY, VLL_DBG);
+
+ error = nl_sock_create(NETLINK_ROUTE, RTNLGRP_LINK, 0, 0, &sock);
+ if (error) {
+ ovs_fatal(error, "could not create rtnetlink socket");
+ }
+
+ for (;;) {
+ struct ofpbuf *buf;
+
+ error = nl_sock_recv(sock, &buf, false);
+ if (error == EAGAIN) {
+ /* Nothing to do. */
+ } else if (error == ENOBUFS) {
+ ovs_error(0, "network monitor socket overflowed");
+ } else if (error) {
+ ovs_fatal(error, "error on network monitor socket");
+ } else {
+ struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
+ struct nlmsghdr *nlh;
+ struct ifinfomsg *iim;
+
+ nlh = ofpbuf_at(buf, 0, NLMSG_HDRLEN);
+ iim = ofpbuf_at(buf, NLMSG_HDRLEN, sizeof *iim);
+ if (!iim) {
+ ovs_error(0, "received bad rtnl message (no ifinfomsg)");
+ ofpbuf_delete(buf);
+ continue;
+ }
+
+ if (!nl_policy_parse(buf, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+ rtnlgrp_link_policy,
+ attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
+ ovs_error(0, "received bad rtnl message (policy)");
+ ofpbuf_delete(buf);
+ continue;
+ }
+ printf("netdev %s changed (%s):\n",
+ nl_attr_get_string(attrs[IFLA_IFNAME]),
+ (nlh->nlmsg_type == RTM_NEWLINK ? "RTM_NEWLINK"
+ : nlh->nlmsg_type == RTM_DELLINK ? "RTM_DELLINK"
+ : nlh->nlmsg_type == RTM_GETLINK ? "RTM_GETLINK"
+ : nlh->nlmsg_type == RTM_SETLINK ? "RTM_SETLINK"
+ : "other"));
+ if (attrs[IFLA_MASTER]) {
+ uint32_t idx = nl_attr_get_u32(attrs[IFLA_MASTER]);
+ char ifname[IFNAMSIZ];
+ if (!if_indextoname(idx, ifname)) {
+ strcpy(ifname, "unknown");
+ }
+ printf("\tmaster=%"PRIu32" (%s)\n", idx, ifname);
+ }
+ ofpbuf_delete(buf);
+ }
+
+ nl_sock_wait(sock, POLLIN);
+ poll_block();
+ }
+}
+
diff --git a/utilities/ovs-appctl.8.in b/utilities/ovs-appctl.8.in
new file mode 100644
index 000000000..9bf97fd2f
--- /dev/null
+++ b/utilities/ovs-appctl.8.in
@@ -0,0 +1,166 @@
+.\" -*- nroff -*-
+.de IQ
+. br
+. ns
+. IP "\\$1"
+..
+.TH ovs\-appctl 8 "April 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-appctl
+
+.SH NAME
+ovs\-appctl \- utility for configuring running Open vSwitch daemons
+
+.SH SYNOPSIS
+\fBovs\-appctl\fR [\fB-h\fR | \fB--help\fR] [\fItarget\fR...] [\fIaction\fR...]
+.sp 1
+The available \fItarget\fR options are:
+.br
+[\fB-t\fR \fIpid\fR | \fB--target=\fIpid\fR]
+.sp 1
+The available \fIaction\fR options are:
+.br
+[\fB-l\fR | \fB--list\fR] [\fB-s\fR
+\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] |
+\fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]
+[\fB-r\fR | \fB--reopen\fR]
+[\fB-e\fR | \fB--execute=\fIcommand\fR]
+
+.SH DESCRIPTION
+The \fBovs\-appctl\fR program connects to one or more running
+Open vSwitch daemons (such as \fBovs\-vswitchd\fR(8)), as specified by the
+user, and sends them commands to query or modify their behavior.
+Its primary purpose is currently to adjust daemons' logging levels.
+
+\fBovs\-appctl\fR applies one or more actions to each of one or more
+target processes. Targets may be specified using:
+
+.IP "\fB-t \fIsocket\fR"
+.IQ "\fB--target=\fIsocket\fR"
+The specified \fIsocket\fR must be the name of a Unix domain socket
+for a \fBovs\-appctl\fR-controllable process. If \fIsocket\fR does not
+begin with \fB/\fR, it is treated as relative to \fB@RUNDIR@\fR.
+
+Each Open vSwitch daemon by default creates a socket named
+\fB@RUNDIR@/\fIprogram\fB.\fIpid\fB.ctl\fR, where \fIprogram\fR is
+the program's name (such as \fBovs\-vswitchd\fR) and \fIpid\fR is the
+daemon's PID.
+
+.PP
+The available actions are:
+
+.IP "\fB-l\fR"
+.IQ "\fB--list\fR"
+Print the list of known logging modules and their current levels to
+stdout.
+
+.IP "\fB-s\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]"
+.IQ "\fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]"
+
+Sets the logging level for \fImodule\fR in \fIfacility\fR to
+\fIlevel\fR. The \fImodule\fR may be any valid module name (as
+displayed by the \fB--list\fR option) or the special name \fBANY\fR to
+set the logging levels for all modules. The \fIfacility\fR may be
+\fBsyslog\fR or \fBconsole\fR to set the levels for logging to the
+system log or to the console, respectively, or \fBANY\fR to set the
+logging levels for both facilities. If it is omitted,
+\fIfacility\fR defaults to \fBANY\fR. The \fIlevel\fR must be one of
+\fBemer\fR, \fBerr\fR, \fBwarn\fR, \fBinfo\fR, or \fBdbg\fR, designating the
+minimum severity of a message for it to be logged. If it is omitted,
+\fIlevel\fR defaults to \fBdbg\fR.
+
+.IP "\fB-s PATTERN:\fIfacility\fB:\fIpattern\fR"
+.IQ "\fB--set=PATTERN:\fIfacility\fB:\fIpattern\fR"
+
+Sets the log pattern for \fIfacility\fR to \fIpattern\fR. Each time a
+message is logged to \fIfacility\fR, \fIpattern\fR determines the
+message's formatting. Most characters in \fIpattern\fR are copied
+literally to the log, but special escapes beginning with \fB%\fR are
+expanded as follows:
+
+.RS
+.IP \fB%A\fR
+The name of the application logging the message, e.g. \fBsecchan\fR.
+
+.IP \fB%c\fR
+The name of the module (as shown by \fBovs\-appctl --list\fR) logging
+the message.
+
+.IP \fB%d\fR
+The current date and time in ISO 8601 format (YYYY-MM-DD HH:MM:SS).
+
+.IP \fB%d{\fIformat\fB}\fR
+The current date and time in the specified \fIformat\fR, which takes
+the same format as the \fItemplate\fR argument to \fBstrftime\fR(3).
+
+.IP \fB%m\fR
+The message being logged.
+
+.IP \fB%N\fR
+A serial number for this message within this run of the program, as a
+decimal number. The first message a program logs has serial number 1,
+the second one has serial number 2, and so on.
+
+.IP \fB%n\fR
+A new-line.
+
+.IP \fB%p\fR
+The level at which the message is logged, e.g. \fBDBG\fR.
+
+.IP \fB%P\fR
+The program's process ID (pid), as a decimal number.
+
+.IP \fB%r\fR
+The number of milliseconds elapsed from the start of the application
+to the time the message was logged.
+
+.IP \fB%%\fR
+A literal \fB%\fR.
+.RE
+
+.IP
+A few options may appear between the \fB%\fR and the format specifier
+character, in this order:
+
+.RS
+.IP \fB-\fR
+Left justify the escape's expansion within its field width. Right
+justification is the default.
+
+.IP \fB0\fR
+Pad the field to the field width with \fB0\fRs. Padding with spaces
+is the default.
+
+.IP \fIwidth\fR
+A number specifies the minimum field width. If the escape expands to
+fewer characters than \fIwidth\fR then it is padded to fill the field
+width. (A field wider than \fIwidth\fR is not truncated to fit.)
+.RE
+
+.IP
+The default pattern for console output is \fB%d{%b %d
+%H:%M:%S}|%05N|%c|%p|%m\fR; for syslog output, \fB%05N|%c|%p|%m\fR.
+
+.IP \fB-r\fR
+.IQ \fB--reopen\fR
+Causes the target application to close and reopen its log file. (This
+is useful after rotating log files, to cause a new log file to be
+used.)
+
+This has no effect if the target application was not invoked with the
+\fB--log-file\fR option.
+
+.IP "\fB-e \fIcommand\fR"
+.IQ "\fB--execute=\fIcommand\fR"
+Passes the specified \fIcommand\fR literally to the target application
+and prints its response to stdout, if successful, or to stderr if an
+error occurs. Use \fB-e help\fR to print a list of available commands.
+
+.SH OPTIONS
+
+.so lib/common.man
+
+.SH "SEE ALSO"
+
+.BR ovs\-controller (8),
+.BR ovs\-dpctl (8),
+.BR secchan (8)
diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c
new file mode 100644
index 000000000..eb544452c
--- /dev/null
+++ b/utilities/ovs-appctl.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <config.h>
+#include "vlog.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "timeval.h"
+#include "unixctl.h"
+#include "util.h"
+
+static void
+usage(char *prog_name, int exit_code)
+{
+ printf("Usage: %s [TARGET] [ACTION...]\n"
+ "Targets:\n"
+ " -t, --target=TARGET Path to Unix domain socket\n"
+ "Actions:\n"
+ " -l, --list List current settings\n"
+ " -s, --set=MODULE[:FACILITY[:LEVEL]]\n"
+ " Set MODULE and FACILITY log level to LEVEL\n"
+ " MODULE may be any valid module name or 'ANY'\n"
+ " FACILITY may be 'syslog', 'console', 'file', or 'ANY' (default)\n"
+ " LEVEL may be 'emer', 'err', 'warn', 'info', or 'dbg' (default)\n"
+ " -r, --reopen Make the program reopen its log file\n"
+ " -e, --execute=COMMAND Execute control COMMAND and print its output\n"
+ "Other options:\n"
+ " -h, --help Print this helpful information\n"
+ " -V, --version Display version information\n",
+ prog_name);
+ exit(exit_code);
+}
+
+static char *
+transact(struct unixctl_client *client, const char *request, bool *ok)
+{
+ int code;
+ char *reply;
+ int error = unixctl_client_transact(client, request, &code, &reply);
+ if (error) {
+ fprintf(stderr, "%s: transaction error: %s\n",
+ unixctl_client_target(client), strerror(error));
+ *ok = false;
+ return xstrdup("");
+ } else {
+ if (code / 100 != 2) {
+ fprintf(stderr, "%s: server returned reply code %03d\n",
+ unixctl_client_target(client), code);
+ }
+ return reply;
+ }
+}
+
+static void
+transact_ack(struct unixctl_client *client, const char *request, bool *ok)
+{
+ free(transact(client, request, ok));
+}
+
+static void
+execute_command(struct unixctl_client *client, const char *request, bool *ok)
+{
+ int code;
+ char *reply;
+ int error = unixctl_client_transact(client, request, &code, &reply);
+ if (error) {
+ fprintf(stderr, "%s: transaction error: %s\n",
+ unixctl_client_target(client), strerror(error));
+ *ok = false;
+ } else {
+ if (code / 100 != 2) {
+ fprintf(stderr, "%s: server returned reply code %03d\n",
+ unixctl_client_target(client), code);
+ fputs(reply, stderr);
+ *ok = false;
+ } else {
+ fputs(reply, stdout);
+ }
+ }
+}
+
+static void
+add_target(struct unixctl_client ***clients, size_t *n_clients,
+ const char *path, bool *ok)
+{
+ struct unixctl_client *client;
+ int error = unixctl_client_create(path, &client);
+ if (error) {
+ fprintf(stderr, "Error connecting to \"%s\": %s\n",
+ path, strerror(error));
+ *ok = false;
+ } else {
+ *clients = xrealloc(*clients, sizeof *clients * (*n_clients + 1));
+ (*clients)[*n_clients] = client;
+ ++*n_clients;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ static const struct option long_options[] = {
+ /* Target options must come first. */
+ {"target", required_argument, NULL, 't'},
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, 'V'},
+
+ /* Action options come afterward. */
+ {"list", no_argument, NULL, 'l'},
+ {"set", required_argument, NULL, 's'},
+ {"reopen", no_argument, NULL, 'r'},
+ {"execute", required_argument, NULL, 'e'},
+ {0, 0, 0, 0},
+ };
+ char *short_options;
+
+ /* Determine targets. */
+ bool ok = true;
+ int n_actions = 0;
+ struct unixctl_client **clients = NULL;
+ size_t n_clients = 0;
+
+ set_program_name(argv[0]);
+ time_init();
+
+ short_options = long_options_to_short_options(long_options);
+ for (;;) {
+ int option;
+ size_t i;
+
+ option = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (option == -1) {
+ break;
+ }
+ if (!strchr("thV", option) && n_clients == 0) {
+ ovs_fatal(0, "no targets specified (use --help for help)");
+ } else {
+ ++n_actions;
+ }
+ switch (option) {
+ case 't':
+ add_target(&clients, &n_clients, optarg, &ok);
+ break;
+
+ case 'l':
+ for (i = 0; i < n_clients; i++) {
+ struct unixctl_client *client = clients[i];
+ char *reply;
+
+ printf("%s:\n", unixctl_client_target(client));
+ reply = transact(client, "vlog/list", &ok);
+ fputs(reply, stdout);
+ free(reply);
+ }
+ break;
+
+ case 's':
+ for (i = 0; i < n_clients; i++) {
+ struct unixctl_client *client = clients[i];
+ char *request = xasprintf("vlog/set %s", optarg);
+ transact_ack(client, request, &ok);
+ free(request);
+ }
+ break;
+
+ case 'r':
+ for (i = 0; i < n_clients; i++) {
+ struct unixctl_client *client = clients[i];
+ char *request = xstrdup("vlog/reopen");
+ transact_ack(client, request, &ok);
+ free(request);
+ }
+ break;
+
+ case 'e':
+ for (i = 0; i < n_clients; i++) {
+ execute_command(clients[i], optarg, &ok);
+ }
+ break;
+
+ case 'h':
+ usage(argv[0], EXIT_SUCCESS);
+ break;
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ NOT_REACHED();
+ }
+ }
+ if (!n_actions) {
+ fprintf(stderr,
+ "warning: no actions specified (use --help for help)\n");
+ }
+ exit(ok ? 0 : 1);
+}
diff --git a/utilities/ovs-cfg-mod.8.in b/utilities/ovs-cfg-mod.8.in
new file mode 100644
index 000000000..5b96f288e
--- /dev/null
+++ b/utilities/ovs-cfg-mod.8.in
@@ -0,0 +1,101 @@
+.\" -*- nroff -*-
+.de IQ
+. br
+. ns
+. IP "\\$1"
+..
+.TH ovs\-cfg\-mod 8 "June 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-cfg\-mod
+.
+.SH NAME
+ovs\-cfg\-mod \- Safely manage a ovs\-vswitchd.conf\-style configuration file
+.
+.SH SYNOPSIS
+\fB ovs\-cfg\-mod \fR[\fB\-T \fItimeout\fR] \fB\-F \fIfile\fR
+[\fIaction\fR] [\fIaction\fR...\fR]
+.
+.SH DESCRIPTION
+A program for managing a \fovs\-vswitchd.conf\fR(5)\-style configuration
+file. \fBovs\-cfg\-mod\fR uses the same locking mechanisms as
+\fBovs\-vswitchd\fR and its related utilities. This allows it to be
+run safely on ``live'' configurations.
+.
+.SH OPTIONS
+.SS "Specifying the Configuration File"
+.
+.IP "\fB\-T\fR \fItimeout\fR
+.IQ "\fB\-\-timeout=\fItimeout\fR
+By default, \fBovs\-cfg\-mod\fR will wait forever to lock the
+configuration file specified on \fB\-F\fR or \fB\-\-config\-file\fR. This
+option makes \fBovs\-cfg\-mod\fR wait no more than \fItimeout\fR
+milliseconds to obtain the lock, after which it exits unsuccessfully.
+.
+If it is present, this option must be specified before \fB\-F\fR or
+\fB\-\-config\-file\fR.
+.
+.IP "\fB\-F\fR \fIfile\fR"
+.IQ "\fB\-\-config\-file=\fIfile\fR"
+Use \fIfile\fR as the configuration file to query or modify.
+.
+This option is required. It must be specified before any action
+options.
+.
+.SS "Specifying Actions"
+A series of one or more action options may follow the configuration
+file options. These are executed in the order provided and under a
+single lock instance, so they appear atomic to external viewers of
+\fIfile\fR.
+.
+As discussed in \fBovs\-vswitchd.conf\fR(5), each line in the
+configuration file consists of a key\-value pair. Actions generally
+take either a \fIkey\fR or \fIentry\fR argument. A \fIkey\fR is a
+dot\-separated description of a configuration option. A \fIentry\fR is
+a key\-value pair, separated by the \fB=\fR sign.
+.
+The following actions are supported:
+.
+.IP "\fB\-a\fR \fIentry\fR"
+.IQ "\fB\-\-add=\fIentry\fR"
+Add \fIentry\fR to \fIfile\fR. Please note that duplicates are
+allowed, so if a unique key is required, a delete must be done first.
+.
+.IP "\fB\-d\fR \fIentry\fR"
+.IQ "\fB\-\-del\-entry=\fIentry\fR"
+Delete \fIentry\fR from \fIfile\fR. Deletes only the first entry
+that matches \fIentry\fR.
+.
+.IP "\fB\-D\fR \fIkey\fR"
+.IQ "\fB\-\-del\-section=\fIkey\fR"
+Delete section \fIkey\fR from \fIfile\fR.
+.
+.IP "\fB\-\-del\-match=\fIpattern\fR"
+Deletes every entry that matches the given shell glob \fIpattern\fR.
+For example, \fB\-\-del\-match=bridge.*.port=*\fR deletes all the ports
+from every bridge, and \fB\-\-del\-match=bonding.bond0.*\fR is equivalent
+to \fB\-\-del\-section=bonding.bond0\fR.
+.
+.IP "\fB\-q\fR \fIkey\fR"
+.IQ "\fB\-\-query=\fIkey\fR"
+Queries \fIfile\fR for entries that match \fIkey\fR. Each matching
+value is printed on a separate line. Duplicates will be printed
+multiple times.
+.
+.IP "\fB\-c\fR"
+.IQ "\fB\-\-changes\fR"
+Logs all of the changes made to the configuration file in a ``unified
+diff''\-like format. Only actual changes are logged, so that if, for
+example, a \fB\-\-del\-match\fR action did not match any key\-value pairs,
+then nothing will be logged due to that action. Furthermore, only the
+net effects of changes are logged: if a key\-value pair was deleted and
+then an identical key\-value pair was added back, then nothing would be
+logged due to those changes.
+.
+This action logs changes that have taken effect at the point where it
+is inserted. Thus, if it is given before any other action, it will
+not log any changes. If \fB\-\-changes\fR is given more than once,
+instances after the first log only the changes since the previous
+instance.
+.
+.SH "SEE ALSO"
+.BR ovs\-vswitchd (8),
+.BR ovs\-vswitchd.conf (5)
diff --git a/utilities/ovs-cfg-mod.c b/utilities/ovs-cfg-mod.c
new file mode 100644
index 000000000..53ebd00a9
--- /dev/null
+++ b/utilities/ovs-cfg-mod.c
@@ -0,0 +1,239 @@
+/* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ */
+#include <config.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "cfg.h"
+#include "command-line.h"
+#include "svec.h"
+#include "timeval.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_cfg_mod
+#include "vlog.h"
+
+/* Configuration when we first read the configuration file. */
+static struct svec orig_cfg = SVEC_EMPTY_INITIALIZER;
+
+static void
+usage(char *prog_name, int exit_code)
+{
+ printf("Usage: %s --config-file=FILE ACTIONS\n"
+ "\nConfig:\n"
+ " -T, --timeout=MS wait at most MS milliseconds for lock\n"
+ " -F, --config-file=FILE use configuration FILE\n"
+ "\nActions:\n"
+ " -a, --add=ENTRY add ENTRY\n"
+ " -d, --del-entry=ENTRY delete ENTRY\n"
+ " -D, --del-section=KEY delete section matching KEY\n"
+ " --del-match=PATTERN delete entries matching shell PATTERN\n"
+ " -q, --query=KEY return all entries matching KEY\n"
+ " -c, --log-changes log changes up to this point\n"
+ "\nOther options:\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n",
+ prog_name);
+ exit(exit_code);
+}
+
+static void
+open_config(char *config_file, int timeout)
+{
+ int error;
+
+ error = cfg_set_file(config_file);
+ if (error) {
+ ovs_fatal(error, "failed to add configuration file \"%s\"",
+ config_file);
+ }
+
+ error = cfg_lock(NULL, timeout);
+ if (error) {
+ ovs_fatal(error, "could not lock configuration file\n");
+ }
+
+ cfg_get_all(&orig_cfg);
+}
+
+static void
+print_vals(char *key)
+{
+ struct svec vals;
+ int i;
+
+ svec_init(&vals);
+ cfg_get_all_strings(&vals, "%s", key);
+
+ for (i=0; i<vals.n; i++) {
+ printf("%s\n", vals.names[i]);
+ }
+}
+
+static void
+log_diffs(void)
+{
+ struct svec new_cfg, removed, added;
+ size_t i;
+
+ svec_init(&new_cfg);
+ cfg_get_all(&new_cfg);
+ svec_diff(&orig_cfg, &new_cfg, &removed, NULL, &added);
+ if (removed.n || added.n) {
+ VLOG_INFO("configuration changes:");
+ for (i = 0; i < removed.n; i++) {
+ VLOG_INFO("-%s", removed.names[i]);
+ }
+ for (i = 0; i < added.n; i++) {
+ VLOG_INFO("+%s", added.names[i]);
+ }
+ } else {
+ VLOG_INFO("configuration unchanged");
+ }
+ svec_destroy(&added);
+ svec_destroy(&removed);
+ svec_swap(&new_cfg, &orig_cfg);
+ svec_destroy(&new_cfg);
+}
+
+int main(int argc, char *argv[])
+{
+ enum {
+ OPT_DEL_MATCH = UCHAR_MAX + 1,
+ };
+ static const struct option long_options[] = {
+ {"config-file", required_argument, 0, 'F'},
+ {"timeout", required_argument, 0, 'T'},
+ {"add", required_argument, 0, 'a'},
+ {"del-entry", required_argument, 0, 'd'},
+ {"del-section", required_argument, 0, 'D'},
+ {"del-match", required_argument, 0, OPT_DEL_MATCH},
+ {"query", required_argument, 0, 'q'},
+ {"changes", no_argument, 0, 'c'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+ };
+ char *short_options;
+ bool config_set = false;
+ int timeout = INT_MAX;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+
+ short_options = long_options_to_short_options(long_options);
+ for (;;) {
+ int option;
+
+ option = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (option == -1) {
+ break;
+ }
+
+ if ((option > UCHAR_MAX || !strchr("FhVv?", option))
+ && config_set == false) {
+ ovs_fatal(0, "no config file specified (use --help for help)");
+ }
+
+ switch (option) {
+ case 'T':
+ if (config_set) {
+ ovs_fatal(0, "--timeout or -T must be specified "
+ "before --file or -F");
+ }
+ timeout = atoi(optarg);
+ break;
+
+ case 'F':
+ open_config(optarg, timeout);
+ config_set = true;
+ break;
+
+ case 'a':
+ cfg_add_entry("%s", optarg);
+ break;
+
+ case 'd':
+ cfg_del_entry("%s", optarg);
+ break;
+
+ case 'D':
+ cfg_del_section("%s", optarg);
+ break;
+
+ case OPT_DEL_MATCH:
+ cfg_del_match("%s", optarg);
+ break;
+
+ case 'q':
+ print_vals(optarg);
+ break;
+
+ case 'c':
+ log_diffs();
+ break;
+
+ case 'h':
+ usage(argv[0], EXIT_SUCCESS);
+ break;
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ NOT_REACHED();
+ }
+ }
+ free(short_options);
+
+ if (optind != argc) {
+ ovs_fatal(0, "non-option arguments not accepted "
+ "(use --help for help)");
+ }
+
+ if (cfg_is_dirty()) {
+ cfg_write();
+ }
+ cfg_unlock();
+
+ exit(0);
+}
diff --git a/utilities/ovs-controller.8.in b/utilities/ovs-controller.8.in
new file mode 100644
index 000000000..31c7a865c
--- /dev/null
+++ b/utilities/ovs-controller.8.in
@@ -0,0 +1,132 @@
+.TH ovs\-controller 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-controller
+
+.SH NAME
+ovs\-controller \- simple OpenFlow controller reference implementation
+
+.SH SYNOPSIS
+.B ovs\-controller
+[\fIoptions\fR] \fImethod\fR \fB[\fImethod\fR]\&...
+
+.SH DESCRIPTION
+\fBovs\-controller\fR manages any number of remote switches over OpenFlow
+protocol, causing them to function as L2 MAC-learning switches or hub.
+
+\fBovs\-controller\fR controls one or more OpenFlow switches, specified as
+one or more of the following OpenFlow connection methods:
+
+.TP
+\fBpssl:\fR[\fIport\fR]
+Listens for SSL connections from remote OpenFlow switches on
+\fIport\fR (default: 6633). The \fB--private-key\fR,
+\fB--certificate\fR, and \fB--ca-cert\fR options are mandatory when
+this form is used.
+
+.TP
+\fBptcp:\fR[\fIport\fR]
+Listens for TCP connections from remote OpenFlow switches on
+\fIport\fR (default: 6633).
+
+.TP
+\fBpunix:\fIfile\fR
+Listens for connections from OpenFlow switches on the Unix domain
+server socket named \fIfile\fR.
+
+.TP
+\fBssl:\fIhost\fR[\fB:\fIport\fR]
+The specified SSL \fIport\fR (default: 6633) on the given remote
+\fIhost\fR. The \fB--private-key\fR, \fB--certificate\fR, and
+\fB--ca-cert\fR options are mandatory when this form is used.
+
+.TP
+\fBtcp:\fIhost\fR[\fB:\fIport\fR]
+The specified TCP \fIport\fR (default: 6633) on the given remote
+\fIhost\fR.
+
+.TP
+\fBunix:\fIfile\fR
+The Unix domain server socket named \fIfile\fR.
+
+.SH OPTIONS
+.TP
+\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
+Specifies a PEM file containing the private key used as the switch's
+identity for SSL connections to the controller.
+
+.TP
+\fB-c\fR, \fB--certificate=\fIcert.pem\fR
+Specifies a PEM file containing a certificate, signed by the
+controller's certificate authority (CA), that certifies the switch's
+private key to identify a trustworthy switch.
+
+.TP
+\fB-C\fR, \fB--ca-cert=\fIswitch-cacert.pem\fR
+Specifies a PEM file containing the CA certificate used to verify that
+the switch is connected to a trustworthy controller.
+
+.TP
+\fB--peer-ca-cert=\fIcontroller-cacert.pem\fR
+Specifies a PEM file that contains one or more additional certificates
+to send to switches. \fIcontroller-cacert.pem\fR should be the CA
+certificate used to sign the controller's own certificate (the
+certificate specified on \fB-c\fR or \fB--certificate\fR).
+
+This option is not useful in normal operation, because the switch must
+already have the controller CA certificate for it to have any
+confidence in the controller's identity. However, this option allows
+a newly installed switch to obtain the controller CA certificate on
+first boot using, e.g., the \fB--bootstrap-ca-cert\fR option to
+\fBsecchan\fR(8).
+
+.IP "\fB-n\fR, \fB--noflow\fR"
+By default, \fBovs\-controller\fR sets up a flow in each OpenFlow switch
+whenever it receives a packet whose destination is known due through
+MAC learning. This option disables flow setup, so that every packet
+in the network passes through the controller.
+
+This option is most useful for debugging. It reduces switching
+performance, so it should not be used in production.
+
+.TP
+\fB--max-idle=\fIsecs\fR|\fBpermanent\fR
+Sets \fIsecs\fR as the number of seconds that a flow set up by the
+controller will remain in the switch's flow table without any matching
+packets being seen. If \fBpermanent\fR is specified, which is not
+recommended, flows will never expire. The default is 60 seconds.
+
+This option affects only flows set up by the OpenFlow controller. In
+some configurations, the switch can set up some flows
+on its own. To set the idle time for those flows, pass
+\fB--max-idle\fR to \fBsecchan\fR (on the switch).
+
+This option has no effect when \fB-n\fR (or \fB--noflow\fR) is in use
+(because the controller does not set up flows in that case).
+
+.IP "\fB-H\fR, \fB--hub\fR"
+By default, the controller acts as an L2 MAC-learning switch. This
+option changes its behavior to that of a hub that floods packets on
+all but the incoming port.
+
+If \fB-H\fR (or \fB--hub\fR) and \fB-n\fR (or \fB--noflow\fR) are used
+together, then the cumulative effect is that every packet passes
+through the controller and every packet is flooded.
+
+This option is most useful for debugging. It reduces switching
+performance, so it should not be used in production.
+
+.so lib/daemon.man
+.so lib/vlog.man
+.so lib/common.man
+
+.SH EXAMPLES
+
+.TP
+To bind locally to port 6633 (the default) and wait for incoming connections from OpenFlow switches:
+
+.B % ovs\-controller ptcp:
+
+.SH "SEE ALSO"
+
+.BR secchan (8),
+.BR ovs\-appctl (8),
+.BR ovs\-dpctl (8)
diff --git a/utilities/ovs-controller.c b/utilities/ovs-controller.c
new file mode 100644
index 000000000..423ce1955
--- /dev/null
+++ b/utilities/ovs-controller.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "daemon.h"
+#include "fault.h"
+#include "learning-switch.h"
+#include "ofpbuf.h"
+#include "openflow/openflow.h"
+#include "poll-loop.h"
+#include "rconn.h"
+#include "timeval.h"
+#include "unixctl.h"
+#include "util.h"
+#include "vconn-ssl.h"
+#include "vconn.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_controller
+
+#define MAX_SWITCHES 16
+#define MAX_LISTENERS 16
+
+struct switch_ {
+ struct lswitch *lswitch;
+ struct rconn *rconn;
+};
+
+/* Learn the ports on which MAC addresses appear? */
+static bool learn_macs = true;
+
+/* Set up flows? (If not, every packet is processed at the controller.) */
+static bool setup_flows = true;
+
+/* --max-idle: Maximum idle time, in seconds, before flows expire. */
+static int max_idle = 60;
+
+static int do_switching(struct switch_ *);
+static void new_switch(struct switch_ *, struct vconn *, const char *name);
+static void parse_options(int argc, char *argv[]);
+static void usage(void) NO_RETURN;
+
+int
+main(int argc, char *argv[])
+{
+ struct unixctl_server *unixctl;
+ struct switch_ switches[MAX_SWITCHES];
+ struct pvconn *listeners[MAX_LISTENERS];
+ int n_switches, n_listeners;
+ int retval;
+ int i;
+
+ set_program_name(argv[0]);
+ register_fault_handlers();
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (argc - optind < 1) {
+ ovs_fatal(0, "at least one vconn argument required; "
+ "use --help for usage");
+ }
+
+ n_switches = n_listeners = 0;
+ for (i = optind; i < argc; i++) {
+ const char *name = argv[i];
+ struct vconn *vconn;
+ int retval;
+
+ retval = vconn_open(name, OFP_VERSION, &vconn);
+ if (!retval) {
+ if (n_switches >= MAX_SWITCHES) {
+ ovs_fatal(0, "max %d switch connections", n_switches);
+ }
+ new_switch(&switches[n_switches++], vconn, name);
+ continue;
+ } else if (retval == EAFNOSUPPORT) {
+ struct pvconn *pvconn;
+ retval = pvconn_open(name, &pvconn);
+ if (!retval) {
+ if (n_listeners >= MAX_LISTENERS) {
+ ovs_fatal(0, "max %d passive connections", n_listeners);
+ }
+ listeners[n_listeners++] = pvconn;
+ }
+ }
+ if (retval) {
+ VLOG_ERR("%s: connect: %s", name, strerror(retval));
+ }
+ }
+ if (n_switches == 0 && n_listeners == 0) {
+ ovs_fatal(0, "no active or passive switch connections");
+ }
+
+ die_if_already_running();
+ daemonize();
+
+ retval = unixctl_server_create(NULL, &unixctl);
+ if (retval) {
+ ovs_fatal(retval, "Could not listen for unixctl connections");
+ }
+
+ while (n_switches > 0 || n_listeners > 0) {
+ int iteration;
+ int i;
+
+ /* Accept connections on listening vconns. */
+ for (i = 0; i < n_listeners && n_switches < MAX_SWITCHES; ) {
+ struct vconn *new_vconn;
+ int retval;
+
+ retval = pvconn_accept(listeners[i], OFP_VERSION, &new_vconn);
+ if (!retval || retval == EAGAIN) {
+ if (!retval) {
+ new_switch(&switches[n_switches++], new_vconn, "tcp");
+ }
+ i++;
+ } else {
+ pvconn_close(listeners[i]);
+ listeners[i] = listeners[--n_listeners];
+ }
+ }
+
+ /* Do some switching work. Limit the number of iterations so that
+ * callbacks registered with the poll loop don't starve. */
+ for (iteration = 0; iteration < 50; iteration++) {
+ bool progress = false;
+ for (i = 0; i < n_switches; ) {
+ struct switch_ *this = &switches[i];
+ int retval = do_switching(this);
+ if (!retval || retval == EAGAIN) {
+ if (!retval) {
+ progress = true;
+ }
+ i++;
+ } else {
+ rconn_destroy(this->rconn);
+ lswitch_destroy(this->lswitch);
+ switches[i] = switches[--n_switches];
+ }
+ }
+ if (!progress) {
+ break;
+ }
+ }
+ for (i = 0; i < n_switches; i++) {
+ struct switch_ *this = &switches[i];
+ lswitch_run(this->lswitch, this->rconn);
+ }
+
+ unixctl_server_run(unixctl);
+
+ /* Wait for something to happen. */
+ if (n_switches < MAX_SWITCHES) {
+ for (i = 0; i < n_listeners; i++) {
+ pvconn_wait(listeners[i]);
+ }
+ }
+ for (i = 0; i < n_switches; i++) {
+ struct switch_ *sw = &switches[i];
+ rconn_run_wait(sw->rconn);
+ rconn_recv_wait(sw->rconn);
+ lswitch_wait(sw->lswitch);
+ }
+ unixctl_server_wait(unixctl);
+ poll_block();
+ }
+
+ return 0;
+}
+
+static void
+new_switch(struct switch_ *sw, struct vconn *vconn, const char *name)
+{
+ sw->rconn = rconn_new_from_vconn(name, vconn);
+ sw->lswitch = lswitch_create(sw->rconn, learn_macs,
+ setup_flows ? max_idle : -1);
+}
+
+static int
+do_switching(struct switch_ *sw)
+{
+ unsigned int packets_sent;
+ struct ofpbuf *msg;
+
+ packets_sent = rconn_packets_sent(sw->rconn);
+
+ msg = rconn_recv(sw->rconn);
+ if (msg) {
+ lswitch_process_packet(sw->lswitch, sw->rconn, msg);
+ ofpbuf_delete(msg);
+ }
+ rconn_run(sw->rconn);
+
+ return (!rconn_is_alive(sw->rconn) ? EOF
+ : rconn_packets_sent(sw->rconn) != packets_sent ? 0
+ : EAGAIN);
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_MAX_IDLE = UCHAR_MAX + 1,
+ OPT_PEER_CA_CERT,
+ VLOG_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"hub", no_argument, 0, 'H'},
+ {"noflow", no_argument, 0, 'n'},
+ {"max-idle", required_argument, 0, OPT_MAX_IDLE},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+ VLOG_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+ VCONN_SSL_LONG_OPTIONS
+ {"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT},
+#endif
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int indexptr;
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, &indexptr);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'H':
+ learn_macs = false;
+ break;
+
+ case 'n':
+ setup_flows = false;
+ break;
+
+ case OPT_MAX_IDLE:
+ if (!strcmp(optarg, "permanent")) {
+ max_idle = OFP_FLOW_PERMANENT;
+ } else {
+ max_idle = atoi(optarg);
+ if (max_idle < 1 || max_idle > 65535) {
+ ovs_fatal(0, "--max-idle argument must be between 1 and "
+ "65535 or the word 'permanent'");
+ }
+ }
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION);
+ exit(EXIT_SUCCESS);
+
+ VLOG_OPTION_HANDLERS
+ DAEMON_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+ VCONN_SSL_OPTION_HANDLERS
+
+ case OPT_PEER_CA_CERT:
+ vconn_ssl_set_peer_ca_cert_file(optarg);
+ break;
+#endif
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: OpenFlow controller\n"
+ "usage: %s [OPTIONS] METHOD\n"
+ "where METHOD is any OpenFlow connection method.\n",
+ program_name, program_name);
+ vconn_usage(true, true, false);
+ daemon_usage();
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -H, --hub act as hub instead of learning switch\n"
+ " -n, --noflow pass traffic, but don't add flows\n"
+ " --max-idle=SECS max idle time for new flows\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
diff --git a/utilities/ovs-discover.8.in b/utilities/ovs-discover.8.in
new file mode 100644
index 000000000..d38ce9ee7
--- /dev/null
+++ b/utilities/ovs-discover.8.in
@@ -0,0 +1,118 @@
+.TH ovs\-discover 8 "May 2008" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-discover
+
+.SH NAME
+ovs\-discover \- controller discovery utility
+
+.SH SYNOPSIS
+.B ovs\-discover
+[\fIoptions\fR] \fInetdev\fR [\fInetdev\fR...]
+
+.SH DESCRIPTION
+The \fBovs\-discover\fR program attempts to discover the location of
+an OpenFlow controller on one of the network devices listed on the
+command line. It repeatedly broadcasts a DHCP request with vendor
+class identifier \fBOpenFlow\fR on each network device until it
+receives an acceptable DHCP response. It will accept any valid DHCP
+reply that has the same vendor class identifier and includes a
+vendor-specific option with code 1 whose contents are a string
+specifying the location of the controller in the same format used on
+the \fBsecchan\fR command line (e.g. \fBssl:192.168.0.1\fR).
+
+When \fBovs\-discover\fR receives an acceptable response, it prints
+the details of the response on \fBstdout\fR. Then, by default, it
+configures the network device on which the response was received with
+the received IP address, netmask, and default gateway, and detaches
+itself to the background.
+
+.SH OPTIONS
+.TP
+\fB--accept-vconn=\fIregex\fR
+By default, \fBovs\-discover\fR accepts any controller location
+advertised over DHCP. With this option, only controllers whose names
+match POSIX extended regular expression \fIregex\fR will be accepted.
+Specifying \fBssl:.*\fR for \fIregex\fR, for example, would cause only
+SSL controller connections to be accepted.
+
+The \fIregex\fR is implicitly anchored at the beginning of the
+controller location string, as if it begins with \fB^\fR.
+
+.TP
+\fB--exit-without-bind\fR
+By default, \fBovs\-discover\fR binds the network device that receives
+the first acceptable response to the IP address received over DHCP.
+With this option, the configuration of the network device is not
+changed at all, except to bring it up if it is initially down, and
+\fBovs\-discover\fR will exit immediately after it receives an
+acceptable DHCP response.
+
+This option is mutually exclusive with \fB--exit-after-bind\fR and
+\fB--no-detach\fR.
+
+.TP
+\fB--exit-after-bind\fR
+By default, after it receives an acceptable DHCP response,
+\fBovs\-discover\fR detaches itself from the foreground session and
+runs in the background maintaining the DHCP lease as necessary. With
+this option, \fBovs\-discover\fR will exit immediately after it
+receives an acceptable DHCP response and configures the network device
+with the received IP address. The address obtained via DHCP could
+therefore be used past the expiration of its lease.
+
+This option is mutually exclusive with \fB--exit-without-bind\fR and
+\fB--no-detach\fR.
+
+.TP
+\fB--no-detach\fR
+By default, \fBovs\-discover\fR runs in the foreground until it obtains
+an acceptable DHCP response, then it detaches itself from the
+foreground session and run as a background process. This option
+prevents \fBovs\-discover\fR from detaching, causing it to run in the
+foreground even after it obtains a DHCP response.
+
+This option is mutually exclusive with \fB--exit-without-bind\fR and
+\fB--exit-after-bind\fR.
+
+.TP
+\fB-P\fR[\fIpidfile\fR], \fB--pidfile\fR[\fB=\fIpidfile\fR]
+Causes a file (by default, \fBovs\-discover.pid\fR) to be created indicating
+the PID of the running process. If \fIpidfile\fR is not specified, or
+if it does not begin with \fB/\fR, then it is created in
+\fB@RUNDIR@\fR.
+
+The \fIpidfile\fR is created when \fBovs\-discover\fR detaches, so
+this this option has no effect when one of \fB--exit-without-bind\fR,
+\fB--exit-after-bind\fR, or \fB--no-detach\fR is also given.
+
+.TP
+\fB-f\fR, \fB--force\fR
+By default, when \fB-P\fR or \fB--pidfile\fR is specified and the
+specified pidfile already exists and is locked by a running process,
+\fBcontroller\fR refuses to start. Specify \fB-f\fR or \fB--force\fR
+to cause it to instead overwrite the pidfile.
+
+When \fB-P\fR or \fB--pidfile\fR is not specified, this option has no
+effect.
+
+.so lib/vlog.man
+.so lib/common.man
+
+.SH BUGS
+
+If the network devices specified on the command line have been added
+to an Open vSwitch datapath with \fBovs\-dpctl add\-if\fR, then controller
+discovery will fail because \fBovs\-discover\fR will not be able to
+see DHCP responses, even though tools such as \fBtcpdump\fR(8) and
+\fBwireshark\fR(1) can see them on the wire. This is because of the
+structure of the Linux kernel networking stack, which hands packets
+first to programs that listen for all arriving packets, then to
+Open vSwitch, then to programs that listen for a specific kind of packet.
+Open vSwitch consumes all the packets handed to it, so tools like
+\fBtcpdump\fR that look at all packets will see packets arriving on
+Open vSwitch interfaces, but \fRovs\-discover\fR, which listens only for
+arriving IP packets, will not.
+
+.SH "SEE ALSO"
+
+.BR secchan (8),
+.BR ovs-pki (8)
diff --git a/utilities/ovs-discover.c b/utilities/ovs-discover.c
new file mode 100644
index 000000000..b664321ff
--- /dev/null
+++ b/utilities/ovs-discover.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <getopt.h>
+#include <limits.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "command-line.h"
+#include "daemon.h"
+#include "dhcp-client.h"
+#include "dhcp.h"
+#include "dirs.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "netdev.h"
+#include "poll-loop.h"
+#include "timeval.h"
+#include "unixctl.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovs_discover
+
+struct iface {
+ const char *name;
+ struct dhclient *dhcp;
+};
+
+/* The interfaces that we serve. */
+static struct iface *ifaces;
+static int n_ifaces;
+
+/* --accept-vconn: Regular expression specifying the class of controller vconns
+ * that we will accept during autodiscovery. */
+static const char *accept_controller_re = ".*";
+static regex_t accept_controller_regex;
+
+/* --exit-without-bind: Exit after discovering the controller, without binding
+ * the network device to an IP address? */
+static bool exit_without_bind;
+
+/* --exit-after-bind: Exit after discovering the controller, after binding the
+ * network device to an IP address? */
+static bool exit_after_bind;
+
+static bool iface_init(struct iface *, const char *netdev_name);
+static void release_ifaces(void *aux UNUSED);
+
+static void parse_options(int argc, char *argv[]);
+static void usage(void) NO_RETURN;
+
+static void modify_dhcp_request(struct dhcp_msg *, void *aux);
+static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
+
+int
+main(int argc, char *argv[])
+{
+ struct unixctl_server *unixctl;
+ int retval;
+ int i;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1) {
+ ovs_fatal(0, "need at least one non-option argument; "
+ "use --help for usage");
+ }
+
+ ifaces = xmalloc(argc * sizeof *ifaces);
+ n_ifaces = 0;
+ for (i = 0; i < argc; i++) {
+ if (iface_init(&ifaces[n_ifaces], argv[i])) {
+ n_ifaces++;
+ }
+ }
+ if (!n_ifaces) {
+ ovs_fatal(0, "failed to initialize any DHCP clients");
+ }
+
+ for (i = 0; i < n_ifaces; i++) {
+ struct iface *iface = &ifaces[i];
+ dhclient_init(iface->dhcp, 0);
+ }
+ fatal_signal_add_hook(release_ifaces, NULL, true);
+
+ retval = regcomp(&accept_controller_regex, accept_controller_re,
+ REG_NOSUB | REG_EXTENDED);
+ if (retval) {
+ size_t length = regerror(retval, &accept_controller_regex, NULL, 0);
+ char *buffer = xmalloc(length);
+ regerror(retval, &accept_controller_regex, buffer, length);
+ ovs_fatal(0, "%s: %s", accept_controller_re, buffer);
+ }
+
+ retval = unixctl_server_create(NULL, &unixctl);
+ if (retval) {
+ ovs_fatal(retval, "Could not listen for unixctl connections");
+ }
+
+ die_if_already_running();
+
+ signal(SIGPIPE, SIG_IGN);
+ for (;;) {
+ fatal_signal_block();
+ for (i = 0; i < n_ifaces; i++) {
+ struct iface *iface = &ifaces[i];
+ dhclient_run(iface->dhcp);
+ if (dhclient_changed(iface->dhcp)) {
+ bool is_bound = dhclient_is_bound(iface->dhcp);
+ int j;
+
+ /* Configure network device. */
+ if (!exit_without_bind) {
+ dhclient_configure_netdev(iface->dhcp);
+ dhclient_update_resolv_conf(iface->dhcp);
+ }
+
+ if (is_bound) {
+ static bool detached = false;
+ struct ds ds;
+
+ /* Disable timeout, since discovery was successful. */
+ time_alarm(0);
+
+ /* Print discovered parameters. */
+ ds_init(&ds);
+ dhcp_msg_to_string(dhclient_get_config(iface->dhcp),
+ true, &ds);
+ fputs(ds_cstr(&ds), stdout);
+ putchar('\n');
+ fflush(stdout);
+ ds_destroy(&ds);
+
+ /* Exit if the user requested it. */
+ if (exit_without_bind) {
+ VLOG_DBG("exiting because of successful binding on %s "
+ "and --exit-without-bind specified",
+ iface->name);
+ exit(0);
+ }
+ if (exit_after_bind) {
+ VLOG_DBG("exiting because of successful binding on %s "
+ "and --exit-after-bind specified",
+ iface->name);
+ exit(0);
+ }
+
+ /* Detach into background, if we haven't already. */
+ if (!detached) {
+ detached = true;
+ daemonize();
+ }
+ }
+
+ /* We only want an address on a single one of our interfaces.
+ * So: if we have an address on this interface, stop looking
+ * for one on the others; if we don't have an address on this
+ * interface, start looking everywhere. */
+ for (j = 0; j < n_ifaces; j++) {
+ struct iface *if2 = &ifaces[j];
+ if (iface != if2) {
+ if (is_bound) {
+ dhclient_release(if2->dhcp);
+ } else {
+ dhclient_init(if2->dhcp, 0);
+ }
+ }
+ }
+ }
+ }
+ unixctl_server_run(unixctl);
+ for (i = 0; i < n_ifaces; i++) {
+ struct iface *iface = &ifaces[i];
+ dhclient_wait(iface->dhcp);
+ }
+ unixctl_server_wait(unixctl);
+ fatal_signal_unblock();
+ poll_block();
+ }
+
+ return 0;
+}
+
+static bool
+iface_init(struct iface *iface, const char *netdev_name)
+{
+ int retval;
+
+ iface->name = netdev_name;
+ iface->dhcp = NULL;
+
+ if (exit_after_bind) {
+ /* Bring this interface up permanently, so that the bound address
+ * persists past program termination. */
+ struct netdev *netdev;
+
+ retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
+ if (retval) {
+ ovs_error(retval, "Could not open %s device", iface->name);
+ return false;
+ }
+ retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
+ if (retval) {
+ ovs_error(retval, "Could not bring %s device up", iface->name);
+ return false;
+ }
+ netdev_close(netdev);
+ }
+
+ retval = dhclient_create(iface->name, modify_dhcp_request,
+ validate_dhcp_offer, NULL, &iface->dhcp);
+ if (retval) {
+ ovs_error(retval, "%s: failed to initialize DHCP client", iface->name);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+release_ifaces(void *aux UNUSED)
+{
+ int i;
+
+ for (i = 0; i < n_ifaces; i++) {
+ struct dhclient *dhcp = ifaces[i].dhcp;
+ dhclient_release(dhcp);
+ if (dhclient_changed(dhcp)) {
+ dhclient_configure_netdev(dhcp);
+ }
+ }
+}
+
+static void
+modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED)
+{
+ dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
+}
+
+static bool
+validate_dhcp_offer(const struct dhcp_msg *msg, void *aux UNUSED)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
+ char *vconn_name;
+ bool accept;
+
+ vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
+ if (!vconn_name) {
+ VLOG_WARN_RL(&rl, "rejecting DHCP offer missing controller vconn");
+ return false;
+ }
+ accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0);
+ free(vconn_name);
+ return accept;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
+ OPT_EXIT_WITHOUT_BIND,
+ OPT_EXIT_AFTER_BIND,
+ OPT_NO_DETACH,
+ };
+ static struct option long_options[] = {
+ {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
+ {"exit-without-bind", no_argument, 0, OPT_EXIT_WITHOUT_BIND},
+ {"exit-after-bind", no_argument, 0, OPT_EXIT_AFTER_BIND},
+ {"no-detach", no_argument, 0, OPT_NO_DETACH},
+ {"timeout", required_argument, 0, 't'},
+ {"pidfile", optional_argument, 0, 'P'},
+ {"force", no_argument, 0, 'f'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+ bool detach_after_bind = true;
+
+ for (;;) {
+ unsigned long int timeout;
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case OPT_ACCEPT_VCONN:
+ accept_controller_re = (optarg[0] == '^'
+ ? optarg
+ : xasprintf("^%s", optarg));
+ break;
+
+ case OPT_EXIT_WITHOUT_BIND:
+ exit_without_bind = true;
+ break;
+
+ case OPT_EXIT_AFTER_BIND:
+ exit_after_bind = true;
+ break;
+
+ case OPT_NO_DETACH:
+ detach_after_bind = false;
+ break;
+
+ case 'P':
+ set_pidfile(optarg);
+ break;
+
+ case 'f':
+ ignore_existing_pidfile();
+ break;
+
+ case 't':
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout <= 0) {
+ ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+ optarg);
+ } else {
+ time_alarm(timeout);
+ }
+ signal(SIGALRM, SIG_DFL);
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+
+ if ((exit_without_bind + exit_after_bind + !detach_after_bind) > 1) {
+ ovs_fatal(0, "--exit-without-bind, --exit-after-bind, and --no-detach "
+ "are mutually exclusive");
+ }
+ if (detach_after_bind) {
+ set_detach();
+ }
+}
+
+static void
+usage(void)
+{
+ printf("%s: a tool for discovering OpenFlow controllers.\n"
+ "usage: %s [OPTIONS] NETDEV [NETDEV...]\n"
+ "where each NETDEV is a network device on which to perform\n"
+ "controller discovery.\n"
+ "\nOrdinarily, ovs-discover runs in the foreground until it\n"
+ "obtains an IP address and discovers an OpenFlow controller via\n"
+ "DHCP, then it prints information about the controller to stdout\n"
+ "and detaches to the background to maintain the IP address lease.\n"
+ "\nNetworking options:\n"
+ " --accept-vconn=REGEX accept matching discovered controllers\n"
+ " --exit-without-bind exit after discovery, without binding\n"
+ " --exit-after-bind exit after discovery, after binding\n"
+ " --no-detach do not detach after discovery\n",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -t, --timeout=SECS give up discovery after SECS seconds\n"
+ " -P, --pidfile[=FILE] create pidfile (default: %s/%s.pid)\n"
+ " -f, --force with -P, start even if already running\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n",
+ ovs_rundir, program_name);
+ exit(EXIT_SUCCESS);
+}
diff --git a/utilities/ovs-dpctl.8.in b/utilities/ovs-dpctl.8.in
new file mode 100644
index 000000000..652ebb13f
--- /dev/null
+++ b/utilities/ovs-dpctl.8.in
@@ -0,0 +1,166 @@
+.TH ovs\-dpctl 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-dpctl
+
+.SH NAME
+ovs\-dpctl \- administer Open vSwitch datapaths
+
+.SH SYNOPSIS
+.B ovs\-dpctl
+[\fIoptions\fR] \fIcommand \fR[\fIswitch\fR] [\fIargs\fR\&...]
+
+.SH DESCRIPTION
+
+The \fBovs\-dpctl\fR program can create, modify, and delete Open vSwitch
+datapaths. A single machine may host up to 256 datapaths (numbered 0
+to 255).
+
+A newly created datapath is associated with only one network device, a
+virtual network device sometimes called the datapath's ``local port''.
+A newly created datapath is not, however, associated with any of the
+host's other network devices. To intercept and process traffic on a
+given network device, use the \fBadd\-if\fR command to explicitly add
+that network device to the datapath.
+
+Do not use \fBovs\-dpctl\fR commands to modify datapaths if
+\fBovs\-vswitchd\fR(8) is in use. Instead, modify the
+\fBovs\-vswitchd\fR configuration file and send \fBSIGHUP\fR to the
+\fBovs\-vswitchd\fR process.
+
+.PP
+Most \fBovs\-dpctl\fR commands that work with datapaths take an argument
+that specifies the name of the datapath, in one of the following
+forms:
+
+.so lib/dpif.man
+
+.PP
+The following commands manage datapaths.
+
+.TP
+\fBadd\-dp \fIdp\fR [\fInetdev\fR...]
+
+Creates datapath \fIdp\fR. The name of the new datapath's local port
+depends on how \fIdp\fR is specified: if it takes the form
+\fBdp\fIN\fR, the local port will be named \fBdp\fIN\fR; if \fIdp\fR
+is \fBnl:\fI, the local port will be named \fBof\fIN\fR; otherwise,
+the local port's name will be \fIdp\fR.
+
+This will fail if the host already has 256 datapaths, if a network
+device with the same name as the new datapath's local port already
+exists, or if \fIdp\fR is given in the form \fBdp\fIN\fR or
+\fBnl:\fIN\fR and a datapath numbered \fIN\fR already exists.
+
+If \fInetdev\fRs are specified, \fBovs\-dpctl\fR adds them to the datapath.
+
+.TP
+\fBdel\-dp \fIdp\fR
+Deletes datapath \fIdp\fR. If \fIdp\fR is associated with any network
+devices, they are automatically removed.
+
+.TP
+\fBadd\-if \fIdp netdev\fR[\fIoption\fR...]...
+Adds each \fInetdev\fR to the set of network devices datapath
+\fIdp\fR monitors, where \fIdp\fR is the name of an existing
+datapath, and \fInetdev\fR is the name of one of the host's
+network devices, e.g. \fBeth0\fR. Once a network device has been added
+to a datapath, the datapath has complete ownership of the network device's
+traffic and the network device appears silent to the rest of the
+system.
+
+A \fInetdev\fR may be followed by a comma-separated list of options.
+The following options are currently supported:
+
+.RS
+.IP "\fBport=\fIportno\fR"
+Specifies \fIportno\fR (a number between 1 and 255) as the port number
+at which \fInetdev\fR will be attached. By default, \fBadd\-if\fR
+automatically selects the lowest available port number.
+
+.IP "\fBinternal\fR"
+Instead of attaching an existing \fInetdev\fR, creates an internal
+port (analogous to the local port) with that name.
+.RE
+
+.TP
+\fBdel\-if \fIdp netdev\fR...
+Removes each \fInetdev\fR from the list of network devices datapath
+\fIdp\fR monitors.
+
+.TP
+\fBshow \fR[\fIdp\fR...]
+Prints a summary of configured datapaths, including their datapath
+numbers and a list of ports connected to each datapath. (The local
+port is identified as port 0.)
+
+If one or more datapaths are specified, information on only those
+datapaths are displayed. Otherwise, \fBovs\-dpctl\fR displays information
+about all configured datapaths.
+
+.IP "\fBdump-flows \fIdp\fR"
+Prints to the console all flow entries in datapath \fIdp\fR's
+flow table.
+
+This command is primarily useful for debugging Open vSwitch. The flow
+table entries that it displays are not
+OpenFlow flow entries. Instead, they are different and considerably
+simpler flows maintained by the Open vSwitch kernel module.
+
+.IP "\fBdel-flows \fIdp\fR"
+Deletes all flow entries from datapath \fIdp\fR's flow table.
+
+This command is primarily useful for debugging Open vSwitch. As
+discussed in \fBdump-flows\fR, these entries are
+not OpenFlow flow entries. By deleting them, the process that set them
+up may be confused about their disappearance.
+
+.IP "\fBdump-groups \fIdp\fR"
+Prints to the console the sets of port groups maintained by datapath
+\fIdp\fR. Ordinarily there are at least 2 port groups in a datapath
+that \fBsecchan\fR or \fBvswitch\fR is controlling: group 0 contains
+all ports except those disabled by STP, and group 1 contains all
+ports. Additional groups might be used in the future.
+
+This command is primarily useful for debugging Open vSwitch. OpenFlow
+does not have a concept of port groups.
+
+.SH OPTIONS
+.TP
+\fB-t\fR, \fB--timeout=\fIsecs\fR
+Limits \fBovs\-dpctl\fR runtime to approximately \fIsecs\fR seconds. If
+the timeout expires, \fBovs\-dpctl\fR will exit with a \fBSIGALRM\fR
+signal.
+
+.so lib/vlog.man
+.so lib/common.man
+
+.SH EXAMPLES
+
+A typical \fBovs\-dpctl\fR command sequence for controlling an
+Open vSwitch kernel module:
+
+.TP
+\fBovs\-dpctl add\-dp dp0\fR
+Creates datapath number 0.
+
+.TP
+\fBovs\-dpctl add\-if dp0 eth0 eth1\fR
+Adds two network devices to the new datapath.
+
+.PP
+At this point one would ordinarily start \fBsecchan\fR(8) on
+\fBdp0\fR, transforming \fBdp0\fR into an OpenFlow switch. Then, when
+the switch and the datapath is no longer needed:
+
+.TP
+\fBovs\-dpctl del\-if dp0 eth0 eth1\fR
+Removes network devices from the datapath.
+
+.TP
+\fBovs\-dpctl del\-dp dp0\fR
+Deletes the datapath.
+
+.SH "SEE ALSO"
+
+.BR secchan (8),
+.BR ovs\-appctl (8),
+.BR ovs\-vswitchd (8)
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
new file mode 100644
index 000000000..ccddd2f29
--- /dev/null
+++ b/utilities/ovs-dpctl.c
@@ -0,0 +1,552 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dirs.h"
+#include "dpif.h"
+#include "dynamic-string.h"
+#include "netdev.h"
+#include "odp-util.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_dpctl
+
+struct command {
+ const char *name;
+ int min_args;
+ int max_args;
+ void (*handler)(int argc, char *argv[]);
+};
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int main(int argc, char *argv[])
+{
+ struct command *p;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ ovs_fatal(0, "missing command name; use --help for help");
+
+ for (p = all_commands; p->name != NULL; p++) {
+ if (!strcmp(p->name, argv[0])) {
+ int n_arg = argc - 1;
+ if (n_arg < p->min_args)
+ ovs_fatal(0, "'%s' command requires at least %d arguments",
+ p->name, p->min_args);
+ else if (n_arg > p->max_args)
+ ovs_fatal(0, "'%s' command takes at most %d arguments",
+ p->name, p->max_args);
+ else {
+ p->handler(argc, argv);
+ if (ferror(stdout)) {
+ ovs_fatal(0, "write to stdout failed");
+ }
+ if (ferror(stderr)) {
+ ovs_fatal(0, "write to stderr failed");
+ }
+ exit(0);
+ }
+ }
+ }
+ ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
+
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ unsigned long int timeout;
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 't':
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout <= 0) {
+ ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+ optarg);
+ } else {
+ time_alarm(timeout);
+ }
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: Open vSwitch datapath management utility\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+ " add-dp DP [IFACE...] add new datapath DP (with IFACEs)\n"
+ " del-dp DP delete local datapath DP\n"
+ " add-if DP IFACE... add each IFACE as a port on DP\n"
+ " del-if DP IFACE... delete each IFACE from DP\n"
+ " show show basic info on all datapaths\n"
+ " show DP... show basic info on each DP\n"
+ " dump-flows DP display flows in DP\n"
+ " del-flows DP delete all flows from DP\n"
+ " dump-groups DP display port groups in DP\n",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -t, --timeout=SECS give up after SECS seconds\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
+
+static void run(int retval, const char *message, ...)
+ PRINTF_FORMAT(2, 3);
+
+static void run(int retval, const char *message, ...)
+{
+ if (retval) {
+ va_list args;
+
+ fprintf(stderr, "%s: ", program_name);
+ va_start(args, message);
+ vfprintf(stderr, message, args);
+ va_end(args);
+ if (retval == EOF) {
+ fputs(": unexpected end of file\n", stderr);
+ } else {
+ fprintf(stderr, ": %s\n", strerror(retval));
+ }
+
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void do_add_if(int argc, char *argv[]);
+
+static int if_up(const char *netdev_name)
+{
+ struct netdev *netdev;
+ int retval;
+
+ retval = netdev_open(netdev_name, NETDEV_ETH_TYPE_NONE, &netdev);
+ if (!retval) {
+ retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
+ netdev_close(netdev);
+ }
+ return retval;
+}
+
+static void
+do_add_dp(int argc UNUSED, char *argv[])
+{
+ struct dpif dpif;
+ run(dpif_create(argv[1], &dpif), "add_dp");
+ dpif_close(&dpif);
+ if (argc > 2) {
+ do_add_if(argc, argv);
+ }
+}
+
+static void
+do_del_dp(int argc UNUSED, char *argv[])
+{
+ struct dpif dpif;
+ run(dpif_open(argv[1], &dpif), "opening datapath");
+ run(dpif_delete(&dpif), "del_dp");
+ dpif_close(&dpif);
+}
+
+static int
+compare_ports(const void *a_, const void *b_)
+{
+ const struct odp_port *a = a_;
+ const struct odp_port *b = b_;
+ return a->port < b->port ? -1 : a->port > b->port;
+}
+
+static void
+query_ports(struct dpif *dpif, struct odp_port **ports, size_t *n_ports)
+{
+ run(dpif_port_list(dpif, ports, n_ports), "listing ports");
+ qsort(*ports, *n_ports, sizeof **ports, compare_ports);
+}
+
+static uint16_t
+get_free_port(struct dpif *dpif)
+{
+ struct odp_port *ports;
+ size_t n_ports;
+ int port_no;
+
+ query_ports(dpif, &ports, &n_ports);
+ for (port_no = 0; port_no <= UINT16_MAX; port_no++) {
+ size_t i;
+ for (i = 0; i < n_ports; i++) {
+ if (ports[i].port == port_no) {
+ goto next_portno;
+ }
+ }
+ free(ports);
+ return port_no;
+
+ next_portno: ;
+ }
+ ovs_fatal(0, "no free datapath ports");
+}
+
+static void
+do_add_if(int argc UNUSED, char *argv[])
+{
+ bool failure = false;
+ struct dpif dpif;
+ int i;
+
+ run(dpif_open(argv[1], &dpif), "opening datapath");
+ for (i = 2; i < argc; i++) {
+ char *save_ptr = NULL;
+ char *devname, *suboptions;
+ int port = -1;
+ int flags = 0;
+ int error;
+
+ devname = strtok_r(argv[i], ",,", &save_ptr);
+ if (!devname) {
+ ovs_error(0, "%s is not a valid network device name", argv[i]);
+ continue;
+ }
+
+ suboptions = strtok_r(NULL, "", &save_ptr);
+ if (suboptions) {
+ enum {
+ AP_PORT,
+ AP_INTERNAL
+ };
+ static char *options[] = {
+ "port",
+ "internal"
+ };
+
+ while (*suboptions != '\0') {
+ char *value;
+
+ switch (getsubopt(&suboptions, options, &value)) {
+ case AP_PORT:
+ if (!value) {
+ ovs_error(0, "'port' suboption requires a value");
+ }
+ port = atoi(value);
+ break;
+
+ case AP_INTERNAL:
+ flags |= ODP_PORT_INTERNAL;
+ break;
+
+ default:
+ ovs_error(0, "unknown suboption '%s'", value);
+ break;
+ }
+ }
+ }
+ if (port < 0) {
+ port = get_free_port(&dpif);
+ }
+
+ error = dpif_port_add(&dpif, devname, port, flags);
+ if (error) {
+ ovs_error(error, "adding %s as port %"PRIu16" of %s failed",
+ devname, port, argv[1]);
+ failure = true;
+ } else if (if_up(devname)) {
+ failure = true;
+ }
+ }
+ dpif_close(&dpif);
+ if (failure) {
+ exit(EXIT_FAILURE);
+ }
+}
+
+static bool
+get_port_number(struct dpif *dpif, const char *name, uint16_t *port)
+{
+ struct odp_port *ports;
+ size_t n_ports;
+ size_t i;
+
+ query_ports(dpif, &ports, &n_ports);
+ for (i = 0; i < n_ports; i++) {
+ if (!strcmp(name, ports[i].devname)) {
+ *port = ports[i].port;
+ free(ports);
+ return true;
+ }
+ }
+ free(ports);
+ ovs_error(0, "no port named %s", name);
+ return false;
+}
+
+static void
+do_del_if(int argc UNUSED, char *argv[])
+{
+ bool failure = false;
+ struct dpif dpif;
+ int i;
+
+ run(dpif_open(argv[1], &dpif), "opening datapath");
+ for (i = 2; i < argc; i++) {
+ const char *name = argv[i];
+ uint16_t port;
+ int error;
+
+ if (!name[strspn(name, "0123456789")]) {
+ port = atoi(name);
+ } else if (!get_port_number(&dpif, name, &port)) {
+ failure = true;
+ continue;
+ }
+
+ error = dpif_port_del(&dpif, port);
+ if (error) {
+ ovs_error(error, "deleting port %s from %s failed", name, argv[1]);
+ failure = true;
+ }
+ }
+ dpif_close(&dpif);
+ if (failure) {
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void
+show_dpif(struct dpif *dpif)
+{
+ struct odp_port *ports;
+ struct odp_stats stats;
+ size_t n_ports;
+ size_t i;
+
+ printf("dp%u:\n", dpif_id(dpif));
+ if (!dpif_get_dp_stats(dpif, &stats)) {
+ printf("\tflows: cur:%"PRIu32", soft-max:%"PRIu32", "
+ "hard-max:%"PRIu32"\n",
+ stats.n_flows, stats.cur_capacity, stats.max_capacity);
+ printf("\tports: cur:%"PRIu32", max:%"PRIu32"\n",
+ stats.n_ports, stats.max_ports);
+ printf("\tgroups: max:%"PRIu16"\n", stats.max_groups);
+ printf("\tlookups: frags:%"PRIu64", hit:%"PRIu64", missed:%"PRIu64", "
+ "lost:%"PRIu64"\n",
+ stats.n_frags, stats.n_hit, stats.n_missed, stats.n_lost);
+ printf("\tqueues: max-miss:%"PRIu16", max-action:%"PRIu16"\n",
+ stats.max_miss_queue, stats.max_action_queue);
+ }
+ query_ports(dpif, &ports, &n_ports);
+ for (i = 0; i < n_ports; i++) {
+ printf("\tport %u: %s", ports[i].port, ports[i].devname);
+ if (ports[i].flags & ODP_PORT_INTERNAL) {
+ printf(" (internal)");
+ }
+ printf("\n");
+ }
+ free(ports);
+ dpif_close(dpif);
+}
+
+static void
+do_show(int argc UNUSED, char *argv[])
+{
+ bool failure = false;
+ if (argc > 1) {
+ int i;
+ for (i = 1; i < argc; i++) {
+ const char *name = argv[i];
+ struct dpif dpif;
+ int error;
+
+ error = dpif_open(name, &dpif);
+ if (!error) {
+ show_dpif(&dpif);
+ } else {
+ ovs_error(error, "opening datapath %s failed", name);
+ failure = true;
+ }
+ }
+ } else {
+ unsigned int i;
+ for (i = 0; i < ODP_MAX; i++) {
+ char name[128];
+ struct dpif dpif;
+ int error;
+
+ sprintf(name, "dp%u", i);
+ error = dpif_open(name, &dpif);
+ if (!error) {
+ show_dpif(&dpif);
+ } else if (error != ENODEV) {
+ ovs_error(error, "opening datapath %s failed", name);
+ failure = true;
+ }
+ }
+ }
+ if (failure) {
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void
+do_dump_flows(int argc UNUSED, char *argv[])
+{
+ struct odp_flow *flows;
+ struct dpif dpif;
+ size_t n_flows;
+ struct ds ds;
+ size_t i;
+
+ run(dpif_open(argv[1], &dpif), "opening datapath");
+ run(dpif_flow_list_all(&dpif, &flows, &n_flows), "listing all flows");
+
+ ds_init(&ds);
+ for (i = 0; i < n_flows; i++) {
+ struct odp_flow *f = &flows[i];
+ enum { MAX_ACTIONS = 4096 / sizeof(union odp_action) };
+ union odp_action actions[MAX_ACTIONS];
+
+ f->actions = actions;
+ f->n_actions = MAX_ACTIONS;
+ dpif_flow_get(&dpif, f);
+
+ ds_clear(&ds);
+ format_odp_flow(&ds, f);
+ printf("%s\n", ds_cstr(&ds));
+ }
+ ds_destroy(&ds);
+ dpif_close(&dpif);
+}
+
+static void
+do_del_flows(int argc UNUSED, char *argv[])
+{
+ struct dpif dpif;
+
+ run(dpif_open(argv[1], &dpif), "opening datapath");
+ run(dpif_flow_flush(&dpif), "deleting all flows");
+ dpif_close(&dpif);
+}
+
+static void
+do_dump_groups(int argc UNUSED, char *argv[])
+{
+ struct odp_stats stats;
+ struct dpif dpif;
+ unsigned int i;
+
+ run(dpif_open(argv[1], &dpif), "opening datapath");
+ run(dpif_get_dp_stats(&dpif, &stats), "get datapath stats");
+ for (i = 0; i < stats.max_groups; i++) {
+ uint16_t ports[UINT16_MAX];
+ size_t n_ports;
+
+ if (!dpif_port_group_get(&dpif, i, ports,
+ ARRAY_SIZE(ports), &n_ports) && n_ports) {
+ size_t j;
+
+ printf("group %u:", i);
+ for (j = 0; j < n_ports; j++) {
+ printf(" %"PRIu16, ports[j]);
+ }
+ printf("\n");
+ }
+ }
+ dpif_close(&dpif);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+ usage();
+}
+
+static struct command all_commands[] = {
+ { "add-dp", 1, INT_MAX, do_add_dp },
+ { "del-dp", 1, 1, do_del_dp },
+ { "add-if", 2, INT_MAX, do_add_if },
+ { "del-if", 2, INT_MAX, do_del_if },
+ { "show", 0, INT_MAX, do_show },
+ { "dump-flows", 1, 1, do_dump_flows },
+ { "del-flows", 1, 1, do_del_flows },
+ { "dump-groups", 1, 1, do_dump_groups },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
diff --git a/utilities/ovs-kill.8.in b/utilities/ovs-kill.8.in
new file mode 100644
index 000000000..af4ec987f
--- /dev/null
+++ b/utilities/ovs-kill.8.in
@@ -0,0 +1,60 @@
+.TH ovs\-kill 8 "May 2008" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-kill
+
+.SH NAME
+ovs\-kill \- kills processes given their pidfiles
+
+.SH SYNOPSIS
+.B ovs\-kill
+[\fIoptions\fR] \fIpidfile\fR [\fIpidfile\fR...]
+
+.SH DESCRIPTION
+The \fBovs\-kill\fR program reads each \fIpidfile\fR specified on the
+command line and sends a signal to the program associated with it, if
+any. It reads one line of text from \fIpidfile\fR, which must contain
+the PID of the process to kill as a text string. It then uses
+\fBfcntl\fR(2) to verify that a process with the PID from the file
+owns a lock on \fIpidfile\fR before it sends the signal.
+
+A \fIpidfile\fR whose name begins with \fB/\fR is used literally.
+Otherwise, \fB@RUNDIR@/\fR is prefixed.
+
+This program exists for use by \fBovs\-switch\-setup\fR, which cannot
+easily implement its functionality since Perl has no portable
+interface to \fBfcntl\fR-based file locking.
+
+.SH OPTIONS
+.TP
+\fB-s \fInumber\fR|\fIname\fR, \fB\-\^\-signal=\fInumber\fR|\fIname\fR
+Sets the signal to be sent to each process. Signals may be given by
+number (e.g. \fB1\fR) or by name (e.g. \fBHUP\fR or \fBSIGHUP\fR).
+By default, \fBSIGTERM\fR is sent.
+
+.TP
+\fB-f\fR, \fB\-\^\-force\fR
+Causes \fBovs\-kill\fR to ignore all errors without printing a message
+to \fBstderr\fR, and to exit with return code 0.
+
+.so lib/common.man
+
+.SH "EXIT CODE"
+
+Without \fB-f\fR or \fB\-\^\-force\fR, \fBovs\-kill\fR exits with
+status 0 if at least one \fIpidfile\fR was given and the process
+represented by every \fIpidfile\fR was signaled successfully,
+otherwise with status 1.
+
+With \fB-f\fR or \fB\-\^\-force\fR, \fBovs\-kill\fR always exits with
+status 0.
+
+.SH BUGS
+
+There is a race between verifying the lock on \fIpidfile\fR and
+actually killing the process.
+
+\fBovs\-kill\fR does not wait for the signaled processes to die before
+exiting.
+
+.SH "SEE ALSO"
+
+.BR ovs\-switch\-setup (8)
diff --git a/utilities/ovs-kill.c b/utilities/ovs-kill.c
new file mode 100644
index 000000000..f30bf0b94
--- /dev/null
+++ b/utilities/ovs-kill.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include "command-line.h"
+#include "daemon.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+/* -s, --signal: signal to send. */
+static int sig_nr = SIGTERM;
+
+/* -f, --force: ignore errors. */
+static bool force;
+
+static void cond_error(int err_no, const char *, ...) PRINTF_FORMAT(2, 3);
+
+static void parse_options(int argc, char *argv[]);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ bool ok = true;
+ int i;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1) {
+ if (!force) {
+ ovs_fatal(0, "need at least one non-option argument; "
+ "use --help for usage");
+ }
+ }
+
+ for (i = 0; i < argc; i++) {
+ char *pidfile;
+ pid_t pid;
+
+ pidfile = make_pidfile_name(argv[i]);
+ pid = read_pidfile(pidfile);
+ if (pid >= 0) {
+ if (kill(pid, sig_nr) < 0) {
+ cond_error(errno, "%s: kill(%ld)", pidfile, (long int) pid);
+ }
+ } else {
+ cond_error(-pid, "could not read %s", pidfile);
+ }
+ free(pidfile);
+ }
+
+ return ok || force ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"signal", required_argument, 0, 's'},
+ {"force", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 's':
+ if (atoi(optarg) || !strcmp(optarg, "0")) {
+ sig_nr = atoi(optarg);
+ } else {
+ struct signal_name {
+ const char *name;
+ int number;
+ };
+
+ static const struct signal_name signals[] = {
+#define SIGNAL(NAME) { #NAME, NAME }
+ SIGNAL(SIGABRT),
+ SIGNAL(SIGALRM),
+ SIGNAL(SIGBUS),
+ SIGNAL(SIGCHLD),
+ SIGNAL(SIGCONT),
+ SIGNAL(SIGFPE),
+ SIGNAL(SIGHUP),
+ SIGNAL(SIGILL),
+ SIGNAL(SIGINT),
+ SIGNAL(SIGKILL),
+ SIGNAL(SIGPIPE),
+ SIGNAL(SIGQUIT),
+ SIGNAL(SIGSEGV),
+ SIGNAL(SIGSTOP),
+ SIGNAL(SIGTERM),
+ SIGNAL(SIGTSTP),
+ SIGNAL(SIGTTIN),
+ SIGNAL(SIGTTOU),
+ SIGNAL(SIGUSR1),
+ SIGNAL(SIGUSR2),
+#ifdef SIGPOLL
+ SIGNAL(SIGPOLL),
+#endif
+ SIGNAL(SIGPROF),
+ SIGNAL(SIGSYS),
+ SIGNAL(SIGTRAP),
+ SIGNAL(SIGURG),
+ SIGNAL(SIGVTALRM),
+ SIGNAL(SIGXCPU),
+ SIGNAL(SIGXFSZ),
+#undef SIGNAL
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(signals); i++) {
+ const struct signal_name *s = &signals[i];
+ if (!strcmp(optarg, s->name)
+ || !strcmp(optarg, s->name + 3)) {
+ sig_nr = s->number;
+ goto got_name;
+ }
+ }
+ ovs_fatal(0, "unknown signal \"%s\"", optarg);
+ got_name: ;
+ }
+ break;
+
+ case 'f':
+ force = true;
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: kills a program using a pidfile\n"
+ "usage: %s [OPTIONS] PIDFILE [PIDFILE...]\n"
+ "where PIDFILE is a pidfile created by an Open vSwitch daemon.\n"
+ "\nOptions:\n"
+ " -s, --signal=NUMBER|NAME signal to send (default: TERM)\n"
+ " -f, --force ignore errors\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n",
+ program_name, program_name);
+ exit(EXIT_SUCCESS);
+}
+
+static void
+cond_error(int err_no, const char *format, ...)
+{
+ if (!force) {
+ va_list args;
+
+ fprintf(stderr, "%s: ", program_name);
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ if (err_no != 0)
+ fprintf(stderr, " (%s)", strerror(err_no));
+ putc('\n', stderr);
+ }
+}
diff --git a/utilities/ovs-monitor b/utilities/ovs-monitor
new file mode 100755
index 000000000..4e0986123
--- /dev/null
+++ b/utilities/ovs-monitor
@@ -0,0 +1,128 @@
+#!/bin/sh
+
+# Copyright (C) 2008, 2009 Nicira Networks, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+
+SECCHAN_PID=/var/run/secchan.pid
+SECCHAN_SOCK=/var/run/secchan.mgmt
+LOG_FILE=/var/log/openflow/monitor
+INTERVAL=1
+FAIL_THRESH=3
+
+usage() {
+ echo usage: $0 options
+ echo
+ echo "OPTIONS:"
+ echo " -h Show this message"
+ echo " -p PID file for secchan (default: $SECCHAN_PID)"
+ echo " -s Unix socket for secchan (default: $SECCHAN_SOCK)"
+ echo " -l File to log messages (default: $LOG_FILE)"
+ echo " -i Interval to send probes in seconds (default: $INTERVAL)"
+ echo " -c Number of failed probes before reboot (default: $FAIL_THRESH)"
+}
+
+log() {
+ echo `date +"%b %d %X"`:$1
+ echo `date +"%b %d %X"`:$1 >> $LOG_FILE
+}
+
+
+while getopts "hp:s:l:i:c:" OPTION; do
+ case $OPTION in
+ h)
+ usage
+ exit 1
+ ;;
+
+ p)
+ SECCHAN_PID=$OPTARG
+ ;;
+
+ s)
+ SECCHAN_SOCK=$OPTARG
+ ;;
+
+ l)
+ LOG_FILE=$OPTARG
+ ;;
+
+ i)
+ INTERVAL=$OPTARG
+ ;;
+
+ c)
+ FAIL_THRESH=$OPTARG
+ ;;
+
+ *)
+ echo "Unknown option: ${OPTION}"
+ esac
+done
+
+
+if [ ! -f $SECCHAN_PID ]; then
+ log "No secchan pid file: ${SECCHAN_PID}"
+ echo "No secchan pid file: ${SECCHAN_PID}"
+fi
+
+if [ ! -S $SECCHAN_SOCK ]; then
+ log "No secchan sock file: ${SECCHAN_SOCK}"
+ echo "No secchan sock file: ${SECCHAN_SOCK}"
+fi
+
+if [ ! -d `dirname $LOG_FILE` ]; then
+ mkdir -p `dirname $LOG_FILE`
+fi
+
+let DP_DOWN=0
+let SECCHAN_DOWN=0
+log "===== Starting Monitor ===="
+while `/bin/true`; do
+ # Only check for liveness if the secchan's PID file exists. The PID
+ # file is removed when secchan is brought down gracefully.
+ if [ -f $SECCHAN_PID ]; then
+ pid=`cat $SECCHAN_PID`
+ if [ -d /proc/$pid ]; then
+ # Check if the secchan and datapath still can communicate
+ if [ -S $SECCHAN_SOCK ]; then
+ ovs-ofctl probe -t 2 unix:$SECCHAN_SOCK
+ if [ $? -ne 0 ]; then
+ log "datapath probe failed"
+ let DP_DOWN++
+ else
+ let DP_DOWN=0
+ fi
+ fi
+ let SECCHAN_DOWN=0
+ else
+ log "secchan probe failed"
+ let SECCHAN_DOWN++
+ fi
+ fi
+
+ if [ $SECCHAN_DOWN -ge $FAIL_THRESH ]; then
+ log "Failed to probe secchan after ${SECCHAN_DOWN} tries...rebooting!"
+ reboot
+ fi
+
+ if [ $DP_DOWN -ge $FAIL_THRESH ]; then
+ log "Failed to probe datapath after ${DP_DOWN} tries...rebooting!"
+ reboot
+ fi
+
+ sleep $INTERVAL
+done
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
new file mode 100644
index 000000000..3a9c305f2
--- /dev/null
+++ b/utilities/ovs-ofctl.8.in
@@ -0,0 +1,489 @@
+.TH ovs\-ofctl 8 "March 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovs\-ofctl
+
+.SH NAME
+ovs\-ofctl \- administer OpenFlow switches
+
+.SH SYNOPSIS
+.B ovs\-ofctl
+[\fIoptions\fR] \fIcommand \fR[\fIswitch\fR] [\fIargs\fR\&...]
+
+.SH DESCRIPTION
+The
+.B ovs\-ofctl
+program is a command line tool for monitoring and administering
+OpenFlow switches. It can also show the current state of an OpenFlow
+switch, including features, configuration, and table entries.
+
+.SS "OpenFlow Switch Management Commands"
+
+These commands allow \fBovs\-ofctl\fR to monitor and administer an OpenFlow
+switch. It is able to show the current state of a switch, including
+features, configuration, and table entries.
+
+Most of these commands take an argument that specifies the method for
+connecting to an OpenFlow switch. The following connection methods
+are supported:
+
+.RS
+.TP
+\fBssl:\fIhost\fR[\fB:\fIport\fR]
+The specified SSL \fIport\fR (default: 6633) on the given remote
+\fIhost\fR. The \fB--private-key\fR, \fB--certificate\fR, and
+\fB--ca-cert\fR options are mandatory when this form is used.
+
+.TP
+\fBtcp:\fIhost\fR[\fB:\fIport\fR]
+The specified TCP \fIport\fR (default: 6633) on the given remote
+\fIhost\fR.
+
+.TP
+\fBunix:\fIfile\fR
+The Unix domain server socket named \fIfile\fR.
+
+.IP "\fIfile\fR"
+This is short for \fBunix:\fIfile\fR, as long as \fIfile\fR does not
+contain a colon.
+
+.IP \fIdp\fR
+This is short for \fBunix:@RUNDIR@/\fIdp\fB.mgmt\fR, as long as
+\fIdp\fR does not contain a colon.
+.RE
+
+.TP
+\fBshow \fIswitch\fR
+Prints to the console information on \fIswitch\fR, including
+information on its flow tables and ports.
+
+.TP
+\fBstatus \fIswitch\fR [\fIkey\fR]
+Prints to the console a series of key-value pairs that report the
+status of \fIswitch\fR. If \fIkey\fR is specified, only the key-value
+pairs whose key names begin with \fIkey\fR are printed. If \fIkey\fR is
+omitted, all key-value pairs are printed.
+
+.TP
+\fBdump-tables \fIswitch\fR
+Prints to the console statistics for each of the flow tables used by
+\fIswitch\fR.
+
+.TP
+\fBdump-ports \fIswitch\fR
+Prints to the console statistics for each of the network devices
+associated with \fIswitch\fR.
+
+.TP
+\fBmod-port \fIswitch\fR \fInetdev\fR \fIaction\fR
+Modify characteristics of an interface monitored by \fIswitch\fR.
+\fInetdev\fR can be referred to by its OpenFlow assigned port number or
+the device name, e.g. \fBeth0\fR. The \fIaction\fR may be any one of the
+following:
+
+.RS
+.IP \fBup\fR
+Enables the interface. This is equivalent to ``ifconfig up'' on a Unix
+system.
+
+.IP \fBdown\fR
+Disables the interface. This is equivalent to ``ifconfig down'' on a Unix
+system.
+
+.IP \fBflood\fR
+When a \fIflood\fR action is specified, traffic will be sent out this
+interface. This is the default posture for monitored ports.
+
+.IP \fBnoflood\fR
+When a \fIflood\fR action is specified, traffic will not be sent out
+this interface. This is primarily useful to prevent loops when a
+spanning tree protocol is not in use.
+
+.RE
+
+.TP
+\fBdump-flows \fIswitch \fR[\fIflows\fR]
+Prints to the console all flow entries in \fIswitch\fR's
+tables that match \fIflows\fR. If \fIflows\fR is omitted, all flows
+in the switch are retrieved. See \fBFlow Syntax\fR, below, for the
+syntax of \fIflows\fR. The output format is described in
+\fBTable Entry Output\fR.
+
+.TP
+\fBdump-aggregate \fIswitch \fR[\fIflows\fR]
+Prints to the console aggregate statistics for flows in
+\fIswitch\fR's tables that match \fIflows\fR. If \fIflows\fR is omitted,
+the statistics are aggregated across all flows in the switch's flow
+tables. See \fBFlow Syntax\fR, below, for the syntax of \fIflows\fR.
+The output format is descrbed in \fBTable Entry Output\fR.
+
+.TP
+\fBadd-flow \fIswitch flow\fR
+Add the flow entry as described by \fIflow\fR to the \fIswitch\fR's
+tables. The flow entry is in the format described in \fBFlow Syntax\fR,
+below.
+
+.TP
+\fBadd-flows \fIswitch file\fR
+Add flow entries as described in \fIfile\fR to \fIswitch\fR's
+tables. Each line in \fIfile\fR is a flow entry in the format
+described in \fBFlow Syntax\fR, below.
+
+.TP
+\fBmod-flows \fIswitch flow\fR
+Modify the actions in entries from the \fIswitch\fR's tables
+that match \fIflow\fR. When invoked with the \fB--strict\fR option,
+wildcards are not treated as active for matching purposes. See
+\fBFlow Syntax\fR, below, for the syntax of \fIflows\fR.
+
+.TP
+\fBdel-flows \fIswitch \fR[\fIflow\fR]
+Deletes entries from the \fIswitch\fR's tables that match
+\fIflow\fR. When invoked with the \fB--strict\fR option, wildcards are
+not treated as active for matching purposes. If \fIflow\fR is
+omitted and the \fB--strict\fR option is not used, all flows in the
+switch's tables are removed. See \fBFlow Syntax\fR, below, for the
+syntax of \fIflows\fR.
+
+.TP
+\fBmonitor \fIswitch\fR [\fImiss-len\fR [\fIsend-exp]]
+Connects to \fIswitch\fR and prints to the console all OpenFlow
+messages received. Usually, \fIswitch\fR should specify a connection
+named on \fBsecchan\fR(8)'s \fB-l\fR or \fB--listen\fR command line
+option.
+
+If \fImiss-len\fR is provided, \fBovs\-ofctl\fR sends an OpenFlow ``set
+configuration'' message at connection setup time that requests
+\fImiss-len\fR bytes of each packet that misses the flow table. The
+OpenFlow reference implementation not send these messages to the
+\fBovs\-ofctl monitor\fR client connection unless a nonzero value is
+specified on this argument.
+
+If \fIsend-exp\fR is specified as \fB1\fR, \fBovs\-ofctl\fR will also
+request to be sent flow expiration messages. If this argument is
+omitted, or \fB0\fR is specified, then \fRovs\-ofctl\fR will not request
+flow expirations.
+
+This command may be useful for debugging switch or controller
+implementations.
+
+.TP
+\fBexecute \fIswitch command \fR[\fIarg\fR...]
+Sends a request to \fIswitch\fR to execute \fIcommand\fR along with
+each \fIarg\fR, if any, then waits for the command to complete and
+reports its completion status on \fBstderr\fR and its output, if any,
+on \fBstdout\fR. The set of available commands and their argument is
+switch-dependent. (This command uses a Nicira extension to OpenFlow
+that may not be available on all switches.)
+
+.SS "OpenFlow Switch and Controller Commands"
+
+The following commands, like those in the previous section, may be
+applied to OpenFlow switches, using any of the connection methods
+described in that section. Unlike those commands, these may also be
+applied to OpenFlow controllers.
+
+.TP
+\fBprobe \fItarget\fR
+Sends a single OpenFlow echo-request message to \fItarget\fR and waits
+for the response. With the \fB-t\fR or \fB--timeout\fR option, this
+command can test whether an OpenFlow switch or controller is up and
+running.
+
+.TP
+\fBping \fItarget \fR[\fIn\fR]
+Sends a series of 10 echo request packets to \fItarget\fR and times
+each reply. The echo request packets consist of an OpenFlow header
+plus \fIn\fR bytes (default: 64) of randomly generated payload. This
+measures the latency of individual requests.
+
+.TP
+\fBbenchmark \fItarget n count\fR
+Sends \fIcount\fR echo request packets that each consist of an
+OpenFlow header plus \fIn\fR bytes of payload and waits for each
+response. Reports the total time required. This is a measure of the
+maximum bandwidth to \fItarget\fR for round-trips of \fIn\fR-byte
+messages.
+
+.SS "Flow Syntax"
+
+Some \fBovs\-ofctl\fR commands accept an argument that describes a flow or
+flows. Such flow descriptions comprise a series
+\fIfield\fB=\fIvalue\fR assignments, separated by commas or white
+space. (Embedding spaces into a flow description normally requires
+quoting to prevent the shell from breaking the description into
+multiple arguments.)
+
+The following field assignments describe how a flow matches a packet.
+If any of these assignments is omitted from the flow syntax, the field
+is treated as a wildcard; thus, if all of them are omitted, the
+resulting flow matches all packets. The string \fB*\fR or \fBANY\fR
+may be specified to explicitly mark any of these fields as a wildcard.
+(\fB*\fR should be quoted to protect it from shell expansion.)
+
+.IP \fBin_port=\fIport_no\fR
+Matches physical port \fIport_no\fR. Switch ports are numbered as
+displayed by \fBovs\-ofctl show\fR.
+
+.IP \fBdl_vlan=\fIvlan\fR
+Matches IEEE 802.1q virtual LAN tag \fIvlan\fR. Specify \fB0xffff\fR
+as \fIvlan\fR to match packets that are not tagged with a virtual LAN;
+otherwise, specify a number between 0 and 4095, inclusive, as the
+12-bit VLAN ID to match.
+
+.IP \fBdl_src=\fImac\fR
+Matches Ethernet source address \fImac\fR, which is specified as 6 pairs
+of hexadecimal digits delimited by colons (e.g. \fB00:0A:E4:25:6B:B0\fR).
+
+.IP \fBdl_dst=\fImac\fR
+Matches Ethernet destination address \fImac\fR.
+
+.IP \fBdl_type=\fIethertype\fR
+Matches Ethernet protocol type \fIethertype\fR, which is specified as an
+integer between 0 and 65535, inclusive, either in decimal or as a
+hexadecimal number prefixed by \fB0x\fR (e.g. \fB0x0806\fR to match ARP
+packets).
+
+.IP \fBnw_src=\fIip\fR[\fB/\fInetmask\fR]
+Matches IPv4 source address \fIip\fR, which may be specified as an
+IP address or host name (e.g. \fB192.168.1.1\fR or
+\fBwww.example.com\fR). The optional \fInetmask\fR allows restricting a
+match to an IPv4 address prefix. The netmask may be specified as a dotted
+quad (e.g. \fB192.168.1.0/255.255.255.0\fR) or as a CIDR block
+(e.g. \fB192.168.1.0/24\fR).
+
+.IP \fBnw_dst=\fIip\fR[\fB/\fInetmask\fR]
+Matches IPv4 destination address \fIip\fR.
+
+.IP \fBnw_proto=\fIproto\fR
+Matches IP protocol type \fIproto\fR, which is specified as a decimal
+number between 0 and 255, inclusive (e.g. 6 to match TCP packets).
+
+.IP \fBtp_src=\fIport\fR
+Matches UDP or TCP source port \fIport\fR, which is specified as a decimal
+number between 0 and 65535, inclusive (e.g. 80 to match packets originating
+from a HTTP server).
+
+.IP \fBtp_dst=\fIport\fR
+Matches UDP or TCP destination port \fIport\fR.
+
+.IP \fBicmp_type=\fItype\fR
+Matches ICMP message with \fItype\fR, which is specified as a decimal
+number between 0 and 255, inclusive.
+
+.IP \fBicmp_code=\fIcode\fR
+Matches ICMP messages with \fIcode\fR.
+
+.PP
+The following shorthand notations are also available:
+
+.IP \fBip\fR
+Same as \fBdl_type=0x0800\fR.
+
+.IP \fBicmp\fR
+Same as \fBdl_type=0x0800,nw_proto=1\fR.
+
+.IP \fBtcp\fR
+Same as \fBdl_type=0x0800,nw_proto=6\fR.
+
+.IP \fBudp\fR
+Same as \fBdl_type=0x0800,nw_proto=17\fR.
+
+.IP \fBarp\fR
+Same as \fBdl_type=0x0806\fR.
+
+.PP
+The \fBadd-flow\fR and \fBadd-flows\fR commands require an additional field:
+
+.IP \fBactions=\fR[\fItarget\fR][\fB,\fItarget\fR...]\fR
+Specifies a comma-separated list of actions to take on a packet when the
+flow entry matches. If no \fItarget\fR is specified, then packets
+matching the flow are dropped. The \fItarget\fR may be a decimal port
+number designating the physical port on which to output the packet, or one
+of the following keywords:
+
+.RS
+.IP \fBoutput\fR:\fIport\fR
+Outputs the packet on the port specified by \fIport\fR.
+
+.IP \fBnormal\fR
+Subjects the packet to the device's normal L2/L3 processing. (This
+action is not implemented by all OpenFlow switches.)
+
+.IP \fBflood\fR
+Outputs the packet on all switch physical ports other than the port on
+which it was received and any ports on which flooding is disabled
+(typically, these would be ports disabled by the IEEE 802.1D spanning
+tree protocol).
+
+.IP \fBall\fR
+Outputs the packet on all switch physical ports other than the port on
+which it was received.
+
+.IP \fBcontroller\fR:\fImax_len\fR
+Sends the packet to the OpenFlow controller as a ``packet in''
+message. If \fImax_len\fR is a number, then it specifies the maximum
+number of bytes that should be sent. If \fImax_len\fR is \fBALL\fR or
+omitted, then the entire packet is sent.
+
+.IP \fBlocal\fR
+Outputs the packet on the ``local port,'' which corresponds to the
+\fBof\fIn\fR network device (see \fBCONTACTING THE CONTROLLER\fR in
+\fBsecchan\fR(8) for information on the \fBof\fIn\fR network device).
+
+.IP \fBdrop\fR
+Discards the packet, so no further processing or forwarding takes place.
+If a drop action is used, no other actions may be specified.
+
+.IP \fBmod_vlan_vid\fR:\fIvlan_vid\fR
+Modifies the VLAN id on a packet. The VLAN tag is added or modified
+as necessary to match the value specified. If the VLAN tag is added,
+a priority of zero is used (see the \fBmod_vlan_pcp\fR action to set
+this).
+
+.IP \fBmod_vlan_pcp\fR:\fIvlan_pcp\fR
+Modifies the VLAN priority on a packet. The VLAN tag is added or modified
+as necessary to match the value specified. Valid values are between 0
+(lowest) and 7 (highest). If the VLAN tag is added, a vid of zero is used
+(see the \fBmod_vlan_vid\fR action to set this).
+
+.IP \fBstrip_vlan\fR
+Strips the VLAN tag from a packet if it is present.
+
+.IP \fBmod_dl_src\fB:\fImac\fR
+Sets the source Ethernet address to \fImac\fR.
+
+.IP \fBmod_dl_dst\fB:\fImac\fR
+Sets the destination Ethernet address to \fImac\fR.
+.RE
+
+.IP
+(The OpenFlow protocol supports other actions that \fBovs\-ofctl\fR does
+not yet expose to the user.)
+
+.PP
+The \fBadd-flow\fR, \fBadd-flows\fR, and \fBdel-flows\fR commands
+support an additional optional field:
+
+.IP \fBpriority=\fIvalue\fR
+The priority at which a wildcarded entry will match in comparison to
+others. \fIvalue\fR is a number between 0 and 65535, inclusive. A higher
+\fIvalue\fR will match before a lower one. An exact-match entry will always
+have priority over an entry containing wildcards, so it has an implicit
+priority value of 65535. When adding a flow, if the field is not specified,
+the flow's priority will default to 32768.
+
+.PP
+The \fBadd-flow\fR and \fBadd-flows\fR commands support additional
+optional fields:
+
+.TP
+\fBidle_timeout=\fIseconds\fR
+Causes the flow to expire after the given number of seconds of
+inactivity. A value of 0 prevents a flow from expiring due to
+inactivity. The default is 60 seconds.
+
+.IP \fBhard_timeout=\fIseconds\fR
+Causes the flow to expire after the given number of seconds,
+regardless of activity. A value of 0 (the default) gives the flow no
+hard expiration deadline.
+
+.PP
+The \fBdump-flows\fR, \fBdump-aggregate\fR, \fBdel-flow\fR
+and \fBdel-flows\fR commands support one additional optional field:
+
+.TP
+\fBout_port=\fIport\fR
+If set, a matching flow must include an output action to \fIport\fR.
+
+.PP
+The \fBdump-flows\fR and \fBdump-aggregate\fR commands support an
+additional optional field:
+
+.IP \fBtable=\fInumber\fR
+If specified, limits the flows about which statistics are gathered to
+those in the table with the given \fInumber\fR. Tables are numbered
+as shown by the \fBdump-tables\fR command.
+
+If this field is not specified, or if \fInumber\fR is given as
+\fB255\fR, statistics are gathered about flows from all tables.
+
+.SS "Table Entry Output"
+
+The \fBdump-tables\fR and \fBdump-aggregate\fR commands print information
+about the entries in a datapath's tables. Each line of output is a
+unique flow entry, which begins with some common information:
+
+.IP \fBduration\fR
+The number of seconds the entry has been in the table.
+
+.IP \fBtable_id\fR
+The table that contains the flow. When a packet arrives, the switch
+begins searching for an entry at the lowest numbered table. Tables are
+numbered as shown by the \fBdump-tables\fR command.
+
+.IP \fBpriority\fR
+The priority of the entry in relation to other entries within the same
+table. A higher value will match before a lower one.
+
+.IP \fBn_packets\fR
+The number of packets that have matched the entry.
+
+.IP \fBn_bytes\fR
+The total number of bytes from packets that have matched the entry.
+
+.PP
+The rest of the line consists of a description of the flow entry as
+described in \fBFlow Syntax\fR, above.
+
+
+.SH OPTIONS
+.TP
+\fB--strict\fR
+Uses strict matching when running flow modification commands.
+
+.TP
+\fB-t\fR, \fB--timeout=\fIsecs\fR
+Limits \fBovs\-ofctl\fR runtime to approximately \fIsecs\fR seconds. If
+the timeout expires, \fBovs\-ofctl\fR will exit with a \fBSIGALRM\fR
+signal.
+
+.TP
+\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
+Specifies a PEM file containing the private key used as the
+identity for SSL connections to a switch.
+
+.TP
+\fB-c\fR, \fB--certificate=\fIcert.pem\fR
+Specifies a PEM file containing a certificate, signed by the
+controller's certificate authority (CA), that certifies the
+private key to identify a trustworthy controller.
+
+.TP
+\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR
+Specifies a PEM file containing the CA certificate used to verify that
+a switch is trustworthy.
+
+.so lib/vlog.man
+.so lib/common.man
+
+.SH EXAMPLES
+
+The following examples assume that an OpenFlow switch on the local
+host has been configured to listen for management connections on a
+Unix domain socket named \fB@RUNDIR@/openflow.sock\fR, e.g. by
+specifying \fB--listen=punix:@RUNDIR@/openflow.sock\fR on the
+\fBsecchan\fR(8) command line.
+
+.TP
+\fBovs\-ofctl dump-tables unix:@RUNDIR@/openflow.sock\fR
+Prints out the switch's table stats. (This is more interesting after
+some traffic has passed through.)
+
+.TP
+\fBovs\-ofctl dump-flows unix:@RUNDIR@/openflow.sock\fR
+Prints the flow entries in the switch.
+
+.SH "SEE ALSO"
+
+.BR ovs\-appctl (8),
+.BR ovs\-controller (8),
+.BR ovs\-vswitchd (8)
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
new file mode 100644
index 000000000..8b343956d
--- /dev/null
+++ b/utilities/ovs-ofctl.c
@@ -0,0 +1,1278 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dirs.h"
+#include "dpif.h"
+#include "dynamic-string.h"
+#include "netdev.h"
+#include "netlink.h"
+#include "odp-util.h"
+#include "ofp-print.h"
+#include "ofpbuf.h"
+#include "openflow/nicira-ext.h"
+#include "openflow/openflow.h"
+#include "packets.h"
+#include "random.h"
+#include "socket-util.h"
+#include "timeval.h"
+#include "util.h"
+#include "vconn-ssl.h"
+#include "vconn.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ofctl
+
+#define DEFAULT_IDLE_TIMEOUT 60
+
+#define MOD_PORT_CMD_UP "up"
+#define MOD_PORT_CMD_DOWN "down"
+#define MOD_PORT_CMD_FLOOD "flood"
+#define MOD_PORT_CMD_NOFLOOD "noflood"
+
+
+/* Settings that may be configured by the user. */
+struct settings {
+ bool strict; /* Use strict matching for flow mod commands */
+};
+
+struct command {
+ const char *name;
+ int min_args;
+ int max_args;
+ void (*handler)(const struct settings *, int argc, char *argv[]);
+};
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[], struct settings *);
+
+int main(int argc, char *argv[])
+{
+ struct settings s;
+ struct command *p;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv, &s);
+ signal(SIGPIPE, SIG_IGN);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ ovs_fatal(0, "missing command name; use --help for help");
+
+ for (p = all_commands; p->name != NULL; p++) {
+ if (!strcmp(p->name, argv[0])) {
+ int n_arg = argc - 1;
+ if (n_arg < p->min_args)
+ ovs_fatal(0, "'%s' command requires at least %d arguments",
+ p->name, p->min_args);
+ else if (n_arg > p->max_args)
+ ovs_fatal(0, "'%s' command takes at most %d arguments",
+ p->name, p->max_args);
+ else {
+ p->handler(&s, argc, argv);
+ if (ferror(stdout)) {
+ ovs_fatal(0, "write to stdout failed");
+ }
+ if (ferror(stderr)) {
+ ovs_fatal(0, "write to stderr failed");
+ }
+ exit(0);
+ }
+ }
+ }
+ ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
+
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[], struct settings *s)
+{
+ enum {
+ OPT_STRICT = UCHAR_MAX + 1
+ };
+ static struct option long_options[] = {
+ {"timeout", required_argument, 0, 't'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"strict", no_argument, 0, OPT_STRICT},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ VCONN_SSL_LONG_OPTIONS
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ /* Set defaults that we can figure out before parsing options. */
+ s->strict = false;
+
+ for (;;) {
+ unsigned long int timeout;
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 't':
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout <= 0) {
+ ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+ optarg);
+ } else {
+ time_alarm(timeout);
+ }
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION);
+ exit(EXIT_SUCCESS);
+
+ case 'v':
+ vlog_set_verbosity(optarg);
+ break;
+
+ case OPT_STRICT:
+ s->strict = true;
+ break;
+
+ VCONN_SSL_OPTION_HANDLERS
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: OpenFlow switch management utility\n"
+ "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+ "\nFor OpenFlow switches:\n"
+ " show SWITCH show OpenFlow information\n"
+ " status SWITCH [KEY] report statistics (about KEY)\n"
+ " dump-desc SWITCH print switch description\n"
+ " dump-tables SWITCH print table stats\n"
+ " mod-port SWITCH IFACE ACT modify port behavior\n"
+ " dump-ports SWITCH print port statistics\n"
+ " dump-flows SWITCH print all flow entries\n"
+ " dump-flows SWITCH FLOW print matching FLOWs\n"
+ " dump-aggregate SWITCH print aggregate flow statistics\n"
+ " dump-aggregate SWITCH FLOW print aggregate stats for FLOWs\n"
+ " add-flow SWITCH FLOW add flow described by FLOW\n"
+ " add-flows SWITCH FILE add flows from FILE\n"
+ " mod-flows SWITCH FLOW modify actions of matching FLOWs\n"
+ " del-flows SWITCH [FLOW] delete matching FLOWs\n"
+ " monitor SWITCH MISSLEN EXP print packets received from SWITCH\n"
+ " execute SWITCH CMD [ARG...] execute CMD with ARGS on SWITCH\n"
+ "\nFor OpenFlow switches and controllers:\n"
+ " probe VCONN probe whether VCONN is up\n"
+ " ping VCONN [N] latency of N-byte echos\n"
+ " benchmark VCONN N COUNT bandwidth of COUNT N-byte echos\n"
+ "where each SWITCH is an active OpenFlow connection method.\n",
+ program_name, program_name);
+ vconn_usage(true, false, false);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " --strict use strict match for flow commands\n"
+ " -t, --timeout=SECS give up after SECS seconds\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
+
+static void run(int retval, const char *message, ...)
+ PRINTF_FORMAT(2, 3);
+
+static void run(int retval, const char *message, ...)
+{
+ if (retval) {
+ va_list args;
+
+ fprintf(stderr, "%s: ", program_name);
+ va_start(args, message);
+ vfprintf(stderr, message, args);
+ va_end(args);
+ if (retval == EOF) {
+ fputs(": unexpected end of file\n", stderr);
+ } else {
+ fprintf(stderr, ": %s\n", strerror(retval));
+ }
+
+ exit(EXIT_FAILURE);
+ }
+}
+
+/* Generic commands. */
+
+static void
+open_vconn(const char *name, struct vconn **vconnp)
+{
+ struct dpif dpif;
+ struct stat s;
+
+ if (strstr(name, ":")) {
+ run(vconn_open_block(name, OFP_VERSION, vconnp),
+ "connecting to %s", name);
+ } else if (!stat(name, &s) && S_ISSOCK(s.st_mode)) {
+ char *vconn_name = xasprintf("unix:%s", name);
+ VLOG_INFO("connecting to %s", vconn_name);
+ run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
+ "connecting to %s", vconn_name);
+ free(vconn_name);
+ } else if (!dpif_open(name, &dpif)) {
+ char dpif_name[IF_NAMESIZE + 1];
+ char *socket_name;
+ char *vconn_name;
+
+ run(dpif_get_name(&dpif, dpif_name, sizeof dpif_name),
+ "obtaining name of %s", dpif_name);
+ dpif_close(&dpif);
+ if (strcmp(dpif_name, name)) {
+ VLOG_INFO("datapath %s is named %s", name, dpif_name);
+ }
+
+ socket_name = xasprintf("%s/%s.mgmt", ovs_rundir, dpif_name);
+ if (stat(socket_name, &s)) {
+ ovs_fatal(errno, "cannot connect to %s: stat failed on %s",
+ name, socket_name);
+ } else if (!S_ISSOCK(s.st_mode)) {
+ ovs_fatal(0, "cannot connect to %s: %s is not a socket",
+ name, socket_name);
+ }
+
+ vconn_name = xasprintf("unix:%s", socket_name);
+ VLOG_INFO("connecting to %s", vconn_name);
+ run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
+ "connecting to %s", vconn_name);
+ free(socket_name);
+ free(vconn_name);
+ } else {
+ ovs_fatal(0, "%s is not a valid connection method", name);
+ }
+}
+
+static void *
+alloc_stats_request(size_t body_len, uint16_t type, struct ofpbuf **bufferp)
+{
+ struct ofp_stats_request *rq;
+ rq = make_openflow((offsetof(struct ofp_stats_request, body)
+ + body_len), OFPT_STATS_REQUEST, bufferp);
+ rq->type = htons(type);
+ rq->flags = htons(0);
+ return rq->body;
+}
+
+static void
+send_openflow_buffer(struct vconn *vconn, struct ofpbuf *buffer)
+{
+ update_openflow_length(buffer);
+ run(vconn_send_block(vconn, buffer), "failed to send packet to switch");
+}
+
+static void
+dump_transaction(const char *vconn_name, struct ofpbuf *request)
+{
+ struct vconn *vconn;
+ struct ofpbuf *reply;
+
+ update_openflow_length(request);
+ open_vconn(vconn_name, &vconn);
+ run(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name);
+ ofp_print(stdout, reply->data, reply->size, 1);
+ vconn_close(vconn);
+}
+
+static void
+dump_trivial_transaction(const char *vconn_name, uint8_t request_type)
+{
+ struct ofpbuf *request;
+ make_openflow(sizeof(struct ofp_header), request_type, &request);
+ dump_transaction(vconn_name, request);
+}
+
+static void
+dump_stats_transaction(const char *vconn_name, struct ofpbuf *request)
+{
+ uint32_t send_xid = ((struct ofp_header *) request->data)->xid;
+ struct vconn *vconn;
+ bool done = false;
+
+ open_vconn(vconn_name, &vconn);
+ send_openflow_buffer(vconn, request);
+ while (!done) {
+ uint32_t recv_xid;
+ struct ofpbuf *reply;
+
+ run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed");
+ recv_xid = ((struct ofp_header *) reply->data)->xid;
+ if (send_xid == recv_xid) {
+ struct ofp_stats_reply *osr;
+
+ ofp_print(stdout, reply->data, reply->size, 1);
+
+ osr = ofpbuf_at(reply, 0, sizeof *osr);
+ done = !osr || !(ntohs(osr->flags) & OFPSF_REPLY_MORE);
+ } else {
+ VLOG_DBG("received reply with xid %08"PRIx32" "
+ "!= expected %08"PRIx32, recv_xid, send_xid);
+ }
+ ofpbuf_delete(reply);
+ }
+ vconn_close(vconn);
+}
+
+static void
+dump_trivial_stats_transaction(const char *vconn_name, uint8_t stats_type)
+{
+ struct ofpbuf *request;
+ alloc_stats_request(0, stats_type, &request);
+ dump_stats_transaction(vconn_name, request);
+}
+
+static void
+do_show(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST);
+ dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST);
+}
+
+static void
+do_status(const struct settings *s UNUSED, int argc, char *argv[])
+{
+ struct nicira_header *request, *reply;
+ struct vconn *vconn;
+ struct ofpbuf *b;
+
+ request = make_openflow(sizeof *request, OFPT_VENDOR, &b);
+ request->vendor = htonl(NX_VENDOR_ID);
+ request->subtype = htonl(NXT_STATUS_REQUEST);
+ if (argc > 2) {
+ ofpbuf_put(b, argv[2], strlen(argv[2]));
+ update_openflow_length(b);
+ }
+ open_vconn(argv[1], &vconn);
+ run(vconn_transact(vconn, b, &b), "talking to %s", argv[1]);
+ vconn_close(vconn);
+
+ if (b->size < sizeof *reply) {
+ ovs_fatal(0, "short reply (%zu bytes)", b->size);
+ }
+ reply = b->data;
+ if (reply->header.type != OFPT_VENDOR
+ || reply->vendor != ntohl(NX_VENDOR_ID)
+ || reply->subtype != ntohl(NXT_STATUS_REPLY)) {
+ ofp_print(stderr, b->data, b->size, 2);
+ ovs_fatal(0, "bad reply");
+ }
+
+ fwrite(reply + 1, b->size - sizeof *reply, 1, stdout);
+}
+
+static void
+do_dump_desc(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ dump_trivial_stats_transaction(argv[1], OFPST_DESC);
+}
+
+static void
+do_dump_tables(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ dump_trivial_stats_transaction(argv[1], OFPST_TABLE);
+}
+
+
+static uint32_t
+str_to_u32(const char *str)
+{
+ char *tail;
+ uint32_t value;
+
+ errno = 0;
+ value = strtoul(str, &tail, 0);
+ if (errno == EINVAL || errno == ERANGE || *tail) {
+ ovs_fatal(0, "invalid numeric format %s", str);
+ }
+ return value;
+}
+
+static void
+str_to_mac(const char *str, uint8_t mac[6])
+{
+ if (sscanf(str, "%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8,
+ &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) {
+ ovs_fatal(0, "invalid mac address %s", str);
+ }
+}
+
+static uint32_t
+str_to_ip(const char *str_, uint32_t *ip)
+{
+ char *str = xstrdup(str_);
+ char *save_ptr = NULL;
+ const char *name, *netmask;
+ struct in_addr in_addr;
+ int n_wild, retval;
+
+ name = strtok_r(str, "//", &save_ptr);
+ retval = name ? lookup_ip(name, &in_addr) : EINVAL;
+ if (retval) {
+ ovs_fatal(0, "%s: could not convert to IP address", str);
+ }
+ *ip = in_addr.s_addr;
+
+ netmask = strtok_r(NULL, "//", &save_ptr);
+ if (netmask) {
+ uint8_t o[4];
+ if (sscanf(netmask, "%"SCNu8".%"SCNu8".%"SCNu8".%"SCNu8,
+ &o[0], &o[1], &o[2], &o[3]) == 4) {
+ uint32_t nm = (o[0] << 24) | (o[1] << 16) | (o[2] << 8) | o[3];
+ int i;
+
+ /* Find first 1-bit. */
+ for (i = 0; i < 32; i++) {
+ if (nm & (1u << i)) {
+ break;
+ }
+ }
+ n_wild = i;
+
+ /* Verify that the rest of the bits are 1-bits. */
+ for (; i < 32; i++) {
+ if (!(nm & (1u << i))) {
+ ovs_fatal(0, "%s: %s is not a valid netmask",
+ str, netmask);
+ }
+ }
+ } else {
+ int prefix = atoi(netmask);
+ if (prefix <= 0 || prefix > 32) {
+ ovs_fatal(0, "%s: network prefix bits not between 1 and 32",
+ str);
+ }
+ n_wild = 32 - prefix;
+ }
+ } else {
+ n_wild = 0;
+ }
+
+ free(str);
+ return n_wild;
+}
+
+static void *
+put_action(struct ofpbuf *b, size_t size, uint16_t type)
+{
+ struct ofp_action_header *ah = ofpbuf_put_zeros(b, size);
+ ah->type = htons(type);
+ ah->len = htons(size);
+ return ah;
+}
+
+static struct ofp_action_output *
+put_output_action(struct ofpbuf *b, uint16_t port)
+{
+ struct ofp_action_output *oao = put_action(b, sizeof *oao, OFPAT_OUTPUT);
+ oao->port = htons(port);
+ return oao;
+}
+
+static void
+put_dl_addr_action(struct ofpbuf *b, uint16_t type, const char *addr)
+{
+ struct ofp_action_dl_addr *oada = put_action(b, sizeof *oada, type);
+ str_to_mac(addr, oada->dl_addr);
+}
+
+
+static bool
+parse_port_name(const char *name, uint16_t *port)
+{
+ struct pair {
+ const char *name;
+ uint16_t value;
+ };
+ static const struct pair pairs[] = {
+#define DEF_PAIR(NAME) {#NAME, OFPP_##NAME}
+ DEF_PAIR(IN_PORT),
+ DEF_PAIR(TABLE),
+ DEF_PAIR(NORMAL),
+ DEF_PAIR(FLOOD),
+ DEF_PAIR(ALL),
+ DEF_PAIR(CONTROLLER),
+ DEF_PAIR(LOCAL),
+ DEF_PAIR(NONE),
+#undef DEF_PAIR
+ };
+ static const int n_pairs = ARRAY_SIZE(pairs);
+ size_t i;
+
+ for (i = 0; i < n_pairs; i++) {
+ if (!strcasecmp(name, pairs[i].name)) {
+ *port = pairs[i].value;
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+str_to_action(char *str, struct ofpbuf *b)
+{
+ char *act, *arg;
+ char *saveptr = NULL;
+ bool drop = false;
+ int n_actions;
+
+ for (act = strtok_r(str, ", \t\r\n", &saveptr), n_actions = 0; act;
+ act = strtok_r(NULL, ", \t\r\n", &saveptr), n_actions++)
+ {
+ uint16_t port;
+
+ if (drop) {
+ ovs_fatal(0, "Drop actions must not be followed by other actions");
+ }
+
+ /* Arguments are separated by colons */
+ arg = strchr(act, ':');
+ if (arg) {
+ *arg = '\0';
+ arg++;
+ }
+
+ if (!strcasecmp(act, "mod_vlan_vid")) {
+ struct ofp_action_vlan_vid *va;
+ va = put_action(b, sizeof *va, OFPAT_SET_VLAN_VID);
+ va->vlan_vid = htons(str_to_u32(arg));
+ } else if (!strcasecmp(act, "mod_vlan_pcp")) {
+ struct ofp_action_vlan_pcp *va;
+ va = put_action(b, sizeof *va, OFPAT_SET_VLAN_PCP);
+ va->vlan_pcp = str_to_u32(arg);
+ } else if (!strcasecmp(act, "strip_vlan")) {
+ struct ofp_action_header *ah;
+ ah = put_action(b, sizeof *ah, OFPAT_STRIP_VLAN);
+ ah->type = htons(OFPAT_STRIP_VLAN);
+ } else if (!strcasecmp(act, "mod_dl_src")) {
+ put_dl_addr_action(b, OFPAT_SET_DL_SRC, arg);
+ } else if (!strcasecmp(act, "mod_dl_dst")) {
+ put_dl_addr_action(b, OFPAT_SET_DL_DST, arg);
+ } else if (!strcasecmp(act, "output")) {
+ put_output_action(b, str_to_u32(arg));
+ } else if (!strcasecmp(act, "drop")) {
+ /* A drop action in OpenFlow occurs by just not setting
+ * an action. */
+ drop = true;
+ if (n_actions) {
+ ovs_fatal(0, "Drop actions must not be preceded by other "
+ "actions");
+ }
+ } else if (!strcasecmp(act, "CONTROLLER")) {
+ struct ofp_action_output *oao;
+ oao = put_output_action(b, OFPP_CONTROLLER);
+
+ /* Unless a numeric argument is specified, we send the whole
+ * packet to the controller. */
+ if (arg && (strspn(act, "0123456789") == strlen(act))) {
+ oao->max_len = htons(str_to_u32(arg));
+ }
+ } else if (parse_port_name(act, &port)) {
+ put_output_action(b, port);
+ } else if (strspn(act, "0123456789") == strlen(act)) {
+ put_output_action(b, str_to_u32(act));
+ } else {
+ ovs_fatal(0, "Unknown action: %s", act);
+ }
+ }
+}
+
+struct protocol {
+ const char *name;
+ uint16_t dl_type;
+ uint8_t nw_proto;
+};
+
+static bool
+parse_protocol(const char *name, const struct protocol **p_out)
+{
+ static const struct protocol protocols[] = {
+ { "ip", ETH_TYPE_IP, 0 },
+ { "arp", ETH_TYPE_ARP, 0 },
+ { "icmp", ETH_TYPE_IP, IP_TYPE_ICMP },
+ { "tcp", ETH_TYPE_IP, IP_TYPE_TCP },
+ { "udp", ETH_TYPE_IP, IP_TYPE_UDP },
+ };
+ const struct protocol *p;
+
+ for (p = protocols; p < &protocols[ARRAY_SIZE(protocols)]; p++) {
+ if (!strcmp(p->name, name)) {
+ *p_out = p;
+ return true;
+ }
+ }
+ *p_out = NULL;
+ return false;
+}
+
+struct field {
+ const char *name;
+ uint32_t wildcard;
+ enum { F_U8, F_U16, F_MAC, F_IP } type;
+ size_t offset, shift;
+};
+
+static bool
+parse_field(const char *name, const struct field **f_out)
+{
+#define F_OFS(MEMBER) offsetof(struct ofp_match, MEMBER)
+ static const struct field fields[] = {
+ { "in_port", OFPFW_IN_PORT, F_U16, F_OFS(in_port), 0 },
+ { "dl_vlan", OFPFW_DL_VLAN, F_U16, F_OFS(dl_vlan), 0 },
+ { "dl_src", OFPFW_DL_SRC, F_MAC, F_OFS(dl_src), 0 },
+ { "dl_dst", OFPFW_DL_DST, F_MAC, F_OFS(dl_dst), 0 },
+ { "dl_type", OFPFW_DL_TYPE, F_U16, F_OFS(dl_type), 0 },
+ { "nw_src", OFPFW_NW_SRC_MASK, F_IP,
+ F_OFS(nw_src), OFPFW_NW_SRC_SHIFT },
+ { "nw_dst", OFPFW_NW_DST_MASK, F_IP,
+ F_OFS(nw_dst), OFPFW_NW_DST_SHIFT },
+ { "nw_proto", OFPFW_NW_PROTO, F_U8, F_OFS(nw_proto), 0 },
+ { "tp_src", OFPFW_TP_SRC, F_U16, F_OFS(tp_src), 0 },
+ { "tp_dst", OFPFW_TP_DST, F_U16, F_OFS(tp_dst), 0 },
+ { "icmp_type", OFPFW_ICMP_TYPE, F_U16, F_OFS(icmp_type), 0 },
+ { "icmp_code", OFPFW_ICMP_CODE, F_U16, F_OFS(icmp_code), 0 }
+ };
+ const struct field *f;
+
+ for (f = fields; f < &fields[ARRAY_SIZE(fields)]; f++) {
+ if (!strcmp(f->name, name)) {
+ *f_out = f;
+ return true;
+ }
+ }
+ *f_out = NULL;
+ return false;
+}
+
+static void
+str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions,
+ uint8_t *table_idx, uint16_t *out_port, uint16_t *priority,
+ uint16_t *idle_timeout, uint16_t *hard_timeout)
+{
+ char *save_ptr = NULL;
+ char *name;
+ uint32_t wildcards;
+
+ if (table_idx) {
+ *table_idx = 0xff;
+ }
+ if (out_port) {
+ *out_port = OFPP_NONE;
+ }
+ if (priority) {
+ *priority = OFP_DEFAULT_PRIORITY;
+ }
+ if (idle_timeout) {
+ *idle_timeout = DEFAULT_IDLE_TIMEOUT;
+ }
+ if (hard_timeout) {
+ *hard_timeout = OFP_FLOW_PERMANENT;
+ }
+ if (actions) {
+ char *act_str = strstr(string, "action");
+ if (!act_str) {
+ ovs_fatal(0, "must specify an action");
+ }
+ *(act_str-1) = '\0';
+
+ act_str = strchr(act_str, '=');
+ if (!act_str) {
+ ovs_fatal(0, "must specify an action");
+ }
+
+ act_str++;
+
+ str_to_action(act_str, actions);
+ }
+ memset(match, 0, sizeof *match);
+ wildcards = OFPFW_ALL;
+ for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name;
+ name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) {
+ const struct protocol *p;
+
+ if (parse_protocol(name, &p)) {
+ wildcards &= ~OFPFW_DL_TYPE;
+ match->dl_type = htons(p->dl_type);
+ if (p->nw_proto) {
+ wildcards &= ~OFPFW_NW_PROTO;
+ match->nw_proto = p->nw_proto;
+ }
+ } else {
+ const struct field *f;
+ char *value;
+
+ value = strtok_r(NULL, ", \t\r\n", &save_ptr);
+ if (!value) {
+ ovs_fatal(0, "field %s missing value", name);
+ }
+
+ if (table_idx && !strcmp(name, "table")) {
+ *table_idx = atoi(value);
+ } else if (out_port && !strcmp(name, "out_port")) {
+ *out_port = atoi(value);
+ } else if (priority && !strcmp(name, "priority")) {
+ *priority = atoi(value);
+ } else if (idle_timeout && !strcmp(name, "idle_timeout")) {
+ *idle_timeout = atoi(value);
+ } else if (hard_timeout && !strcmp(name, "hard_timeout")) {
+ *hard_timeout = atoi(value);
+ } else if (parse_field(name, &f)) {
+ void *data = (char *) match + f->offset;
+ if (!strcmp(value, "*") || !strcmp(value, "ANY")) {
+ wildcards |= f->wildcard;
+ } else {
+ wildcards &= ~f->wildcard;
+ if (f->wildcard == OFPFW_IN_PORT
+ && parse_port_name(value, (uint16_t *) data)) {
+ /* Nothing to do. */
+ } else if (f->type == F_U8) {
+ *(uint8_t *) data = str_to_u32(value);
+ } else if (f->type == F_U16) {
+ *(uint16_t *) data = htons(str_to_u32(value));
+ } else if (f->type == F_MAC) {
+ str_to_mac(value, data);
+ } else if (f->type == F_IP) {
+ wildcards |= str_to_ip(value, data) << f->shift;
+ } else {
+ NOT_REACHED();
+ }
+ }
+ } else {
+ ovs_fatal(0, "unknown keyword %s", name);
+ }
+ }
+ }
+ match->wildcards = htonl(wildcards);
+}
+
+static void
+do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
+{
+ struct ofp_flow_stats_request *req;
+ uint16_t out_port;
+ struct ofpbuf *request;
+
+ req = alloc_stats_request(sizeof *req, OFPST_FLOW, &request);
+ str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL,
+ &req->table_id, &out_port, NULL, NULL, NULL);
+ memset(&req->pad, 0, sizeof req->pad);
+ req->out_port = htons(out_port);
+
+ dump_stats_transaction(argv[1], request);
+}
+
+static void
+do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
+{
+ struct ofp_aggregate_stats_request *req;
+ struct ofpbuf *request;
+ uint16_t out_port;
+
+ req = alloc_stats_request(sizeof *req, OFPST_AGGREGATE, &request);
+ str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL,
+ &req->table_id, &out_port, NULL, NULL, NULL);
+ memset(&req->pad, 0, sizeof req->pad);
+ req->out_port = htons(out_port);
+
+ dump_stats_transaction(argv[1], request);
+}
+
+static void
+do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ struct vconn *vconn;
+ struct ofpbuf *buffer;
+ struct ofp_flow_mod *ofm;
+ uint16_t priority, idle_timeout, hard_timeout;
+ struct ofp_match match;
+
+ /* Parse and send. str_to_flow() will expand and reallocate the data in
+ * 'buffer', so we can't keep pointers to across the str_to_flow() call. */
+ make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
+ str_to_flow(argv[2], &match, buffer,
+ NULL, NULL, &priority, &idle_timeout, &hard_timeout);
+ ofm = buffer->data;
+ ofm->match = match;
+ ofm->command = htons(OFPFC_ADD);
+ ofm->idle_timeout = htons(idle_timeout);
+ ofm->hard_timeout = htons(hard_timeout);
+ ofm->buffer_id = htonl(UINT32_MAX);
+ ofm->priority = htons(priority);
+ ofm->reserved = htonl(0);
+
+ open_vconn(argv[1], &vconn);
+ send_openflow_buffer(vconn, buffer);
+ vconn_close(vconn);
+}
+
+static void
+do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ struct vconn *vconn;
+ FILE *file;
+ char line[1024];
+
+ file = fopen(argv[2], "r");
+ if (file == NULL) {
+ ovs_fatal(errno, "%s: open", argv[2]);
+ }
+
+ open_vconn(argv[1], &vconn);
+ while (fgets(line, sizeof line, file)) {
+ struct ofpbuf *buffer;
+ struct ofp_flow_mod *ofm;
+ uint16_t priority, idle_timeout, hard_timeout;
+ struct ofp_match match;
+
+ char *comment;
+
+ /* Delete comments. */
+ comment = strchr(line, '#');
+ if (comment) {
+ *comment = '\0';
+ }
+
+ /* Drop empty lines. */
+ if (line[strspn(line, " \t\n")] == '\0') {
+ continue;
+ }
+
+ /* Parse and send. str_to_flow() will expand and reallocate the data
+ * in 'buffer', so we can't keep pointers to across the str_to_flow()
+ * call. */
+ ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
+ str_to_flow(line, &match, buffer,
+ NULL, NULL, &priority, &idle_timeout, &hard_timeout);
+ ofm = buffer->data;
+ ofm->match = match;
+ ofm->command = htons(OFPFC_ADD);
+ ofm->idle_timeout = htons(idle_timeout);
+ ofm->hard_timeout = htons(hard_timeout);
+ ofm->buffer_id = htonl(UINT32_MAX);
+ ofm->priority = htons(priority);
+ ofm->reserved = htonl(0);
+
+ send_openflow_buffer(vconn, buffer);
+ }
+ vconn_close(vconn);
+ fclose(file);
+}
+
+static void
+do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
+{
+ uint16_t priority, idle_timeout, hard_timeout;
+ struct vconn *vconn;
+ struct ofpbuf *buffer;
+ struct ofp_flow_mod *ofm;
+
+ /* Parse and send. */
+ ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
+ str_to_flow(argv[2], &ofm->match, buffer,
+ NULL, NULL, &priority, &idle_timeout, &hard_timeout);
+ if (s->strict) {
+ ofm->command = htons(OFPFC_MODIFY_STRICT);
+ } else {
+ ofm->command = htons(OFPFC_MODIFY);
+ }
+ ofm->idle_timeout = htons(idle_timeout);
+ ofm->hard_timeout = htons(hard_timeout);
+ ofm->buffer_id = htonl(UINT32_MAX);
+ ofm->priority = htons(priority);
+ ofm->reserved = htonl(0);
+
+ open_vconn(argv[1], &vconn);
+ send_openflow_buffer(vconn, buffer);
+ vconn_close(vconn);
+}
+
+static void do_del_flows(const struct settings *s, int argc, char *argv[])
+{
+ struct vconn *vconn;
+ uint16_t priority;
+ uint16_t out_port;
+ struct ofpbuf *buffer;
+ struct ofp_flow_mod *ofm;
+
+ /* Parse and send. */
+ ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
+ str_to_flow(argc > 2 ? argv[2] : "", &ofm->match, NULL, NULL,
+ &out_port, &priority, NULL, NULL);
+ if (s->strict) {
+ ofm->command = htons(OFPFC_DELETE_STRICT);
+ } else {
+ ofm->command = htons(OFPFC_DELETE);
+ }
+ ofm->idle_timeout = htons(0);
+ ofm->hard_timeout = htons(0);
+ ofm->buffer_id = htonl(UINT32_MAX);
+ ofm->out_port = htons(out_port);
+ ofm->priority = htons(priority);
+ ofm->reserved = htonl(0);
+
+ open_vconn(argv[1], &vconn);
+ send_openflow_buffer(vconn, buffer);
+ vconn_close(vconn);
+}
+
+static void
+do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ struct vconn *vconn;
+
+ open_vconn(argv[1], &vconn);
+ if (argc > 2) {
+ int miss_send_len = atoi(argv[2]);
+ int send_flow_exp = argc > 3 ? atoi(argv[3]) : 0;
+ struct ofp_switch_config *osc;
+ struct ofpbuf *buf;
+
+ osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &buf);
+ osc->flags = htons(send_flow_exp ? OFPC_SEND_FLOW_EXP : 0);
+ osc->miss_send_len = htons(miss_send_len);
+ send_openflow_buffer(vconn, buf);
+ }
+ for (;;) {
+ struct ofpbuf *b;
+ run(vconn_recv_block(vconn, &b), "vconn_recv");
+ ofp_print(stderr, b->data, b->size, 2);
+ ofpbuf_delete(b);
+ }
+}
+
+static void
+do_dump_ports(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ dump_trivial_stats_transaction(argv[1], OFPST_PORT);
+}
+
+static void
+do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ struct ofpbuf *request;
+ struct vconn *vconn;
+ struct ofpbuf *reply;
+
+ make_openflow(sizeof(struct ofp_header), OFPT_ECHO_REQUEST, &request);
+ open_vconn(argv[1], &vconn);
+ run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]);
+ if (reply->size != sizeof(struct ofp_header)) {
+ ovs_fatal(0, "reply does not match request");
+ }
+ ofpbuf_delete(reply);
+ vconn_close(vconn);
+}
+
+static void
+do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ struct ofpbuf *request, *reply;
+ struct ofp_switch_features *osf;
+ struct ofp_port_mod *opm;
+ struct vconn *vconn;
+ char *endptr;
+ int n_ports;
+ int port_idx;
+ int port_no;
+
+
+ /* Check if the argument is a port index. Otherwise, treat it as
+ * the port name. */
+ port_no = strtol(argv[2], &endptr, 10);
+ if (port_no == 0 && endptr == argv[2]) {
+ port_no = -1;
+ }
+
+ /* Send a "Features Request" to get the information we need in order
+ * to modify the port. */
+ make_openflow(sizeof(struct ofp_header), OFPT_FEATURES_REQUEST, &request);
+ open_vconn(argv[1], &vconn);
+ run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]);
+
+ osf = reply->data;
+ n_ports = (reply->size - sizeof *osf) / sizeof *osf->ports;
+
+ for (port_idx = 0; port_idx < n_ports; port_idx++) {
+ if (port_no != -1) {
+ /* Check argument as a port index */
+ if (osf->ports[port_idx].port_no == htons(port_no)) {
+ break;
+ }
+ } else {
+ /* Check argument as an interface name */
+ if (!strncmp((char *)osf->ports[port_idx].name, argv[2],
+ sizeof osf->ports[0].name)) {
+ break;
+ }
+
+ }
+ }
+ if (port_idx == n_ports) {
+ ovs_fatal(0, "couldn't find monitored port: %s", argv[2]);
+ }
+
+ opm = make_openflow(sizeof(struct ofp_port_mod), OFPT_PORT_MOD, &request);
+ opm->port_no = osf->ports[port_idx].port_no;
+ memcpy(opm->hw_addr, osf->ports[port_idx].hw_addr, sizeof opm->hw_addr);
+ opm->config = htonl(0);
+ opm->mask = htonl(0);
+ opm->advertise = htonl(0);
+
+ printf("modifying port: %s\n", osf->ports[port_idx].name);
+
+ if (!strncasecmp(argv[3], MOD_PORT_CMD_UP, sizeof MOD_PORT_CMD_UP)) {
+ opm->mask |= htonl(OFPPC_PORT_DOWN);
+ } else if (!strncasecmp(argv[3], MOD_PORT_CMD_DOWN,
+ sizeof MOD_PORT_CMD_DOWN)) {
+ opm->mask |= htonl(OFPPC_PORT_DOWN);
+ opm->config |= htonl(OFPPC_PORT_DOWN);
+ } else if (!strncasecmp(argv[3], MOD_PORT_CMD_FLOOD,
+ sizeof MOD_PORT_CMD_FLOOD)) {
+ opm->mask |= htonl(OFPPC_NO_FLOOD);
+ } else if (!strncasecmp(argv[3], MOD_PORT_CMD_NOFLOOD,
+ sizeof MOD_PORT_CMD_NOFLOOD)) {
+ opm->mask |= htonl(OFPPC_NO_FLOOD);
+ opm->config |= htonl(OFPPC_NO_FLOOD);
+ } else {
+ ovs_fatal(0, "unknown mod-port command '%s'", argv[3]);
+ }
+
+ send_openflow_buffer(vconn, request);
+
+ ofpbuf_delete(reply);
+ vconn_close(vconn);
+}
+
+static void
+do_ping(const struct settings *s UNUSED, int argc, char *argv[])
+{
+ size_t max_payload = 65535 - sizeof(struct ofp_header);
+ unsigned int payload;
+ struct vconn *vconn;
+ int i;
+
+ payload = argc > 2 ? atoi(argv[2]) : 64;
+ if (payload > max_payload) {
+ ovs_fatal(0, "payload must be between 0 and %zu bytes", max_payload);
+ }
+
+ open_vconn(argv[1], &vconn);
+ for (i = 0; i < 10; i++) {
+ struct timeval start, end;
+ struct ofpbuf *request, *reply;
+ struct ofp_header *rq_hdr, *rpy_hdr;
+
+ rq_hdr = make_openflow(sizeof(struct ofp_header) + payload,
+ OFPT_ECHO_REQUEST, &request);
+ random_bytes(rq_hdr + 1, payload);
+
+ gettimeofday(&start, NULL);
+ run(vconn_transact(vconn, ofpbuf_clone(request), &reply), "transact");
+ gettimeofday(&end, NULL);
+
+ rpy_hdr = reply->data;
+ if (reply->size != request->size
+ || memcmp(rpy_hdr + 1, rq_hdr + 1, payload)
+ || rpy_hdr->xid != rq_hdr->xid
+ || rpy_hdr->type != OFPT_ECHO_REPLY) {
+ printf("Reply does not match request. Request:\n");
+ ofp_print(stdout, request, request->size, 2);
+ printf("Reply:\n");
+ ofp_print(stdout, reply, reply->size, 2);
+ }
+ printf("%d bytes from %s: xid=%08"PRIx32" time=%.1f ms\n",
+ reply->size - sizeof *rpy_hdr, argv[1], rpy_hdr->xid,
+ (1000*(double)(end.tv_sec - start.tv_sec))
+ + (.001*(end.tv_usec - start.tv_usec)));
+ ofpbuf_delete(request);
+ ofpbuf_delete(reply);
+ }
+ vconn_close(vconn);
+}
+
+static void
+do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+{
+ size_t max_payload = 65535 - sizeof(struct ofp_header);
+ struct timeval start, end;
+ unsigned int payload_size, message_size;
+ struct vconn *vconn;
+ double duration;
+ int count;
+ int i;
+
+ payload_size = atoi(argv[2]);
+ if (payload_size > max_payload) {
+ ovs_fatal(0, "payload must be between 0 and %zu bytes", max_payload);
+ }
+ message_size = sizeof(struct ofp_header) + payload_size;
+
+ count = atoi(argv[3]);
+
+ printf("Sending %d packets * %u bytes (with header) = %u bytes total\n",
+ count, message_size, count * message_size);
+
+ open_vconn(argv[1], &vconn);
+ gettimeofday(&start, NULL);
+ for (i = 0; i < count; i++) {
+ struct ofpbuf *request, *reply;
+ struct ofp_header *rq_hdr;
+
+ rq_hdr = make_openflow(message_size, OFPT_ECHO_REQUEST, &request);
+ memset(rq_hdr + 1, 0, payload_size);
+ run(vconn_transact(vconn, request, &reply), "transact");
+ ofpbuf_delete(reply);
+ }
+ gettimeofday(&end, NULL);
+ vconn_close(vconn);
+
+ duration = ((1000*(double)(end.tv_sec - start.tv_sec))
+ + (.001*(end.tv_usec - start.tv_usec)));
+ printf("Finished in %.1f ms (%.0f packets/s) (%.0f bytes/s)\n",
+ duration, count / (duration / 1000.0),
+ count * message_size / (duration / 1000.0));
+}
+
+static void
+do_execute(const struct settings *s UNUSED, int argc, char *argv[])
+{
+ struct vconn *vconn;
+ struct ofpbuf *request;
+ struct nicira_header *nicira;
+ struct nx_command_reply *ncr;
+ uint32_t xid;
+ int i;
+
+ nicira = make_openflow(sizeof *nicira, OFPT_VENDOR, &request);
+ xid = nicira->header.xid;
+ nicira->vendor = htonl(NX_VENDOR_ID);
+ nicira->subtype = htonl(NXT_COMMAND_REQUEST);
+ ofpbuf_put(request, argv[2], strlen(argv[2]));
+ for (i = 3; i < argc; i++) {
+ ofpbuf_put_zeros(request, 1);
+ ofpbuf_put(request, argv[i], strlen(argv[i]));
+ }
+ update_openflow_length(request);
+
+ open_vconn(argv[1], &vconn);
+ run(vconn_send_block(vconn, request), "send");
+
+ for (;;) {
+ struct ofpbuf *reply;
+ uint32_t status;
+
+ run(vconn_recv_xid(vconn, xid, &reply), "recv_xid");
+ if (reply->size < sizeof *ncr) {
+ ovs_fatal(0, "reply is too short (%zu bytes < %zu bytes)",
+ reply->size, sizeof *ncr);
+ }
+ ncr = reply->data;
+ if (ncr->nxh.header.type != OFPT_VENDOR
+ || ncr->nxh.vendor != htonl(NX_VENDOR_ID)
+ || ncr->nxh.subtype != htonl(NXT_COMMAND_REPLY)) {
+ ovs_fatal(0, "reply is invalid");
+ }
+
+ status = ntohl(ncr->status);
+ if (status & NXT_STATUS_STARTED) {
+ /* Wait for a second reply. */
+ continue;
+ } else if (status & NXT_STATUS_EXITED) {
+ fprintf(stderr, "process terminated normally with exit code %d",
+ status & NXT_STATUS_EXITSTATUS);
+ } else if (status & NXT_STATUS_SIGNALED) {
+ fprintf(stderr, "process terminated by signal %d",
+ status & NXT_STATUS_TERMSIG);
+ } else if (status & NXT_STATUS_ERROR) {
+ fprintf(stderr, "error executing command");
+ } else {
+ fprintf(stderr, "process terminated for unknown reason");
+ }
+ if (status & NXT_STATUS_COREDUMP) {
+ fprintf(stderr, " (core dumped)");
+ }
+ putc('\n', stderr);
+
+ fwrite(ncr + 1, reply->size - sizeof *ncr, 1, stdout);
+ break;
+ }
+}
+
+static void
+do_help(const struct settings *s UNUSED, int argc UNUSED, char *argv[] UNUSED)
+{
+ usage();
+}
+
+static struct command all_commands[] = {
+ { "show", 1, 1, do_show },
+ { "status", 1, 2, do_status },
+ { "monitor", 1, 3, do_monitor },
+ { "dump-desc", 1, 1, do_dump_desc },
+ { "dump-tables", 1, 1, do_dump_tables },
+ { "dump-flows", 1, 2, do_dump_flows },
+ { "dump-aggregate", 1, 2, do_dump_aggregate },
+ { "add-flow", 2, 2, do_add_flow },
+ { "add-flows", 2, 2, do_add_flows },
+ { "mod-flows", 2, 2, do_mod_flows },
+ { "del-flows", 1, 2, do_del_flows },
+ { "dump-ports", 1, 1, do_dump_ports },
+ { "mod-port", 3, 3, do_mod_port },
+ { "probe", 1, 1, do_probe },
+ { "ping", 1, 2, do_ping },
+ { "benchmark", 3, 3, do_benchmark },
+ { "execute", 2, INT_MAX, do_execute },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
diff --git a/utilities/ovs-parse-leaks.in b/utilities/ovs-parse-leaks.in
new file mode 100755
index 000000000..4e06847c2
--- /dev/null
+++ b/utilities/ovs-parse-leaks.in
@@ -0,0 +1,285 @@
+#! @PERL@
+
+use strict;
+use warnings;
+
+if (grep($_ eq '--help', @ARGV)) {
+ print <<EOF;
+$0, for parsing leak checker logs
+usage: $0 [BINARY] < LOG
+where LOG is a file produced by an Open vSwitch program's --check-leaks option
+ and BINARY is the binary that wrote LOG.
+EOF
+ exit 0;
+}
+
+die "$0: zero or one arguments required; use --help for help\n" if @ARGV > 1;
+die "$0: $ARGV[0] does not exist" if @ARGV > 0 && ! -e $ARGV[0];
+
+our ($binary);
+our ($a2l) = search_path("addr2line");
+my ($no_syms) = "symbols will not be translated";
+if (!@ARGV) {
+ print "no binary specified; $no_syms\n";
+} elsif (! -e $ARGV[0]) {
+ print "$ARGV[0] does not exist; $no_syms";
+} elsif (!defined($a2l)) {
+ print "addr2line not found in PATH; $no_syms";
+} else {
+ $binary = $ARGV[0];
+}
+
+our ($objdump) = search_path("objdump");
+print "objdump not found; dynamic library symbols will not be translated\n"
+ if !defined($objdump);
+
+our %blocks;
+our @segments;
+while (<STDIN>) {
+ my $ptr = "((?:0x)?[0-9a-fA-F]+|\\(nil\\))";
+ my $callers = ":((?: $ptr)+)";
+ if (/^malloc\((\d+)\) -> $ptr$callers$/) {
+ allocated($., $2, $1, $3);
+ } elsif (/^claim\($ptr\)$callers$/) {
+ claimed($., $1, $2);
+ } elsif (/realloc\($ptr, (\d+)\) -> $ptr$callers$/) {
+ my ($callers) = $4;
+ freed($., $1, $callers);
+ allocated($., $3, $2, $callers);
+ } elsif (/^free\($ptr\)$callers$/) {
+ freed($., $1, $2);
+ } elsif (/^segment: $ptr-$ptr $ptr [-r][-w][-x][sp] (.*)/) {
+ add_segment(hex($1), hex($2), hex($3), $4);
+ } else {
+ print "stdin:$.: syntax error\n";
+ }
+}
+if (%blocks) {
+ my $n_blocks = scalar(keys(%blocks));
+ my $n_bytes = 0;
+ $n_bytes += $_->{SIZE} foreach values(%blocks);
+ print "$n_bytes bytes in $n_blocks blocks not freed at end of run\n";
+ my %blocks_by_callers;
+ foreach my $block (values(%blocks)) {
+ my ($trimmed_callers) = trim_callers($block->{CALLERS});
+ push (@{$blocks_by_callers{$trimmed_callers}}, $block);
+ }
+ foreach my $callers (sort {@{$b} <=> @{$a}} (values(%blocks_by_callers))) {
+ $n_blocks = scalar(@{$callers});
+ $n_bytes = 0;
+ $n_bytes += $_->{SIZE} foreach @{$callers};
+ print "$n_bytes bytes in these $n_blocks blocks were not freed:\n";
+ my $i = 0;
+ my $max = 5;
+ foreach my $block (sort {$a->{LINE} <=> $b->{LINE}} (@{$callers})) {
+ printf "\t%d-byte block at 0x%08x allocated on stdin:%d\n",
+ $block->{SIZE}, $block->{BASE}, $block->{LINE};
+ last if $i++ > $max;
+ }
+ print "\t...and ", $n_blocks - $max, " others...\n"
+ if $n_blocks > $max;
+ print "The blocks listed above were allocated by:\n";
+ print_callers("\t", ${$callers}[0]->{CALLERS});
+ }
+}
+sub interp_pointer {
+ my ($s_ptr) = @_;
+ return $s_ptr eq '(nil)' ? 0 : hex($s_ptr);
+}
+
+sub allocated {
+ my ($line, $s_base, $size, $callers) = @_;
+ my ($base) = interp_pointer($s_base);
+ return if !$base;
+ my ($info) = {LINE => $line,
+ BASE => $base,
+ SIZE => $size,
+ CALLERS => $callers};
+ if (exists($blocks{$base})) {
+ print "In-use address returned by allocator:\n";
+ print "\tInitial allocation:\n";
+ print_block("\t\t", $blocks{$base});
+ print "\tNew allocation:\n";
+ print_block("\t\t", $info);
+ }
+ $blocks{$base} = $info;
+}
+
+sub claimed {
+ my ($line, $s_base, $callers) = @_;
+ my ($base) = interp_pointer($s_base);
+ return if !$base;
+ if (exists($blocks{$base})) {
+ $blocks{$base}{LINE} = $line;
+ $blocks{$base}{CALLERS} = $callers;
+ } else {
+ printf "Claim asserted on not-in-use block 0x%08x by:\n", $base;
+ print_callers('', $callers);
+ }
+}
+
+sub freed {
+ my ($line, $s_base, $callers) = @_;
+ my ($base) = interp_pointer($s_base);
+ return if !$base;
+
+ if (!delete($blocks{$base})) {
+ printf "Bad free of not-allocated address 0x%08x on stdin:%d by:\n", $base, $line;
+ print_callers('', $callers);
+ }
+}
+
+sub print_block {
+ my ($prefix, $info) = @_;
+ printf '%s%d-byte block at 0x%08x allocated on stdin:%d by:' . "\n",
+ $prefix, $info->{SIZE}, $info->{BASE}, $info->{LINE};
+ print_callers($prefix, $info->{CALLERS});
+}
+
+sub print_callers {
+ my ($prefix, $callers) = @_;
+ foreach my $pc (split(' ', $callers)) {
+ print "$prefix\t", lookup_pc($pc), "\n";
+ }
+}
+
+our (%cache);
+sub lookup_pc {
+ my ($s_pc) = @_;
+ if (defined($binary)) {
+ my ($pc) = hex($s_pc);
+ my ($output) = "$s_pc: ";
+ if (!exists($cache{$pc})) {
+ open(A2L, "$a2l -fe $binary --demangle $s_pc|");
+ chomp(my $function = <A2L>);
+ chomp(my $line = <A2L>);
+ close(A2L);
+ if ($function eq '??') {
+ ($function, $line) = lookup_pc_by_segment($pc);
+ }
+ $line =~ s/^(\.\.\/)*//;
+ $line = "..." . substr($line, -25) if length($line) > 28;
+ $cache{$pc} = "$s_pc: $function ($line)";
+ }
+ return $cache{$pc};
+ } else {
+ return "$s_pc";
+ }
+}
+
+sub trim_callers {
+ my ($in) = @_;
+ my (@out);
+ foreach my $pc (split(' ', $in)) {
+ my $xlated = lookup_pc($pc);
+ if ($xlated =~ /\?\?/) {
+ push(@out, "...") if !@out || $out[$#out] ne '...';
+ } else {
+ push(@out, $pc);
+ }
+ }
+ return join(' ', @out);
+}
+
+sub search_path {
+ my ($target) = @_;
+ for my $dir (split (':', $ENV{PATH})) {
+ my ($file) = "$dir/$target";
+ return $file if -e $file;
+ }
+ return undef;
+}
+
+sub add_segment {
+ my ($vm_start, $vm_end, $vm_pgoff, $file) = @_;
+ for (my $i = 0; $i <= $#segments; $i++) {
+ my ($s) = $segments[$i];
+ next if $vm_end <= $s->{START} || $vm_start >= $s->{END};
+ if ($vm_start <= $s->{START} && $vm_end >= $s->{END}) {
+ splice(@segments, $i, 1);
+ --$i;
+ } else {
+ $s->{START} = $vm_end if $vm_end > $s->{START};
+ $s->{END} = $vm_start if $vm_start <= $s->{END};
+ }
+ }
+ push(@segments, {START => $vm_start,
+ END => $vm_end,
+ PGOFF => $vm_pgoff,
+ FILE => $file});
+ @segments = sort { $a->{START} <=> $b->{START} } @segments;
+}
+
+sub binary_search {
+ my ($array, $value) = @_;
+ my $l = 0;
+ my $r = $#{$array};
+ while ($l <= $r) {
+ my $m = int(($l + $r) / 2);
+ my $e = $array->[$m];
+ if ($value < $e->{START}) {
+ $r = $m - 1;
+ } elsif ($value >= $e->{END}) {
+ $l = $m + 1;
+ } else {
+ return $e;
+ }
+ }
+ return undef;
+}
+
+sub read_sections {
+ my ($file) = @_;
+ my (@sections);
+ open(OBJDUMP, "$objdump -h $file|");
+ while (<OBJDUMP>) {
+ my $ptr = "([0-9a-fA-F]+)";
+ my ($name, $size, $vma, $lma, $file_off)
+ = /^\s*\d+\s+(\S+)\s+$ptr\s+$ptr\s+$ptr\s+$ptr/
+ or next;
+ push(@sections, {START => hex($file_off),
+ END => hex($file_off) + hex($size),
+ NAME => $name});
+ }
+ close(OBJDUMP);
+ return [sort { $a->{START} <=> $b->{START} } @sections ];
+}
+
+our %file_to_sections;
+sub segment_to_section {
+ my ($file, $file_offset) = @_;
+ if (!defined($file_to_sections{$file})) {
+ $file_to_sections{$file} = read_sections($file);
+ }
+ return binary_search($file_to_sections{$file}, $file_offset);
+}
+
+sub address_to_segment {
+ my ($pc) = @_;
+ return binary_search(\@segments, $pc);
+}
+
+sub lookup_pc_by_segment {
+ return ('??', 0) if !defined($objdump);
+
+ my ($pc) = @_;
+ my ($segment) = address_to_segment($pc);
+ return ('??', 0) if !defined($segment) || $segment->{FILE} eq '';
+
+ my ($file_offset) = $pc - $segment->{START} + $segment->{PGOFF};
+ my ($section) = segment_to_section($segment->{FILE}, $file_offset);
+ return ('??', 0) if !defined($section);
+
+ my ($section_offset) = $file_offset - $section->{START};
+ open(A2L, sprintf("%s -fe %s --demangle --section=$section->{NAME} 0x%x|",
+ $a2l, $segment->{FILE}, $section_offset));
+ chomp(my $function = <A2L>);
+ chomp(my $line = <A2L>);
+ close(A2L);
+
+ return ($function, $line);
+}
+
+# Local Variables:
+# mode: perl
+# End:
diff --git a/utilities/ovs-pki-cgi.in b/utilities/ovs-pki-cgi.in
new file mode 100755
index 000000000..837b3f92b
--- /dev/null
+++ b/utilities/ovs-pki-cgi.in
@@ -0,0 +1,41 @@
+#! @PERL@
+
+use CGI;
+use Digest::SHA1;
+use Fcntl;
+
+$CGI::POST_MAX = 65536; # Limit POSTs to 64 kB.
+
+use strict;
+use warnings;
+
+my $pkidir = '@PKIDIR@';
+my $q = new CGI;
+
+die unless $q->request_method() eq 'POST';
+
+my $type = $q->param('type');
+die unless defined $type;
+die unless $type eq 'switch' or $type eq 'controller';
+
+my $req = $q->param('req');
+die unless defined $req;
+die unless $req =~ /^-----BEGIN CERTIFICATE REQUEST-----$/m;
+die unless $req =~ /^-----END CERTIFICATE REQUEST-----$/m;
+
+my $digest = Digest::SHA1::sha1_hex($req);
+my $incoming = "$pkidir/${type}ca/incoming";
+my $dst = "$incoming/$digest-req.pem";
+
+sysopen(REQUEST, "$dst.tmp", O_RDWR | O_CREAT | O_EXCL, 0600)
+ or die "sysopen $dst.tmp: $!";
+print REQUEST $req;
+close(REQUEST) or die "close $dst.tmp: $!";
+
+rename("$dst.tmp", $dst) or die "rename $dst.tmp to $dst: $!";
+
+print $q->header('text/html', '204 No response');
+
+# Local Variables:
+# mode: perl
+# End:
diff --git a/utilities/ovs-pki.8.in b/utilities/ovs-pki.8.in
new file mode 100644
index 000000000..35bf0ea41
--- /dev/null
+++ b/utilities/ovs-pki.8.in
@@ -0,0 +1,323 @@
+.TH ovs\-pki 8 "May 2008" "Open vSwitch" "Open vSwitch Manual"
+
+.SH NAME
+ovs\-pki \- OpenFlow public key infrastructure management utility
+
+.SH SYNOPSIS
+\fBovs\-pki\fR [\fIOPTIONS\fR] \fICOMMAND\fR [\fIARGS\fR]
+.sp
+Stand\-alone commands with their arguments:
+.br
+\fBovs\-pki\fR \fBinit\fR
+.br
+\fBovs\-pki\fR \fBreq\fR \fINAME\fR
+.br
+\fBovs\-pki\fR \fBsign\fR \fINAME\fR [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBreq+sign\fR \fINAME\fR [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBverify\fR \fINAME\fR [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBfingerprint\fR \fIFILE\fR
+.br
+\fBovs\-pki\fR \self-sign\fR \fINAME\fR
+.sp
+The following additional commands manage an online PKI:
+.br
+\fBovs\-pki\fR \fBls\fR [\fIPREFIX\fR] [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBflush\fR [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBreject\fR \fIPREFIX\fR [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBapprove\fR \fIPREFIX\fR [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBprompt\fR [\fITYPE\fR]
+.br
+\fBovs\-pki\fR \fBexpire\fR [\fIAGE\fR]
+.sp
+Each \fITYPE\fR above is a certificate type, either \fBswitch\fR
+(default) or \fBcontroller\fR.
+.sp
+The available options are:
+.br
+[\fB\-k\fR \fItype\fR | \fB\-\^\-key=\fItype\fR]
+[\fB\-B\fR \fInbits\fR | \fB\-\^\-bits=\fInbits\fR]
+[\fB\-D\fR \fIfile\fR | \fB\-\^\-dsaparam=\fIfile\fR]
+[\fB\-b\fR | \fB\-\^\-batch\fR]
+[\fB\-f\fR | \fB\-\^\-force\fR]
+[\fB\-d\fR \fIdir\fR | \fB\-\^\-dir=\fR\fIdir\fR]
+[\fB\-l\fR \fIfile\fR | \fB\-\^\-log=\fIfile\fR]
+[\fB\-h\fR | \fB\-\^\-help\fR]
+.br
+Some options do not apply to every command.
+
+.SH DESCRIPTION
+The \fBovs\-pki\fR program sets up and manages a public key
+infrastructure for use with OpenFlow. It is intended to be a simple
+interface for organizations that do not have an established public key
+infrastructure. Other PKI tools can substitute for or supplement the
+use of \fBovs\-pki\fR.
+
+\fBovs\-pki\fR uses \fBopenssl\fR(1) for certificate management and key
+generation.
+
+.SH "OFFLINE COMMANDS"
+
+The following \fBovs\-pki\fR commands support manual PKI
+administration:
+
+.TP
+\fBinit\fR
+Initializes a new PKI (by default in directory \fB@PKIDIR@\fR) and populates
+it with a pair of certificate authorities for controllers and
+switches.
+
+This command should ideally be run on a high\-security machine separate
+from any OpenFlow controller or switch, called the CA machine. The
+files \fBpki/controllerca/cacert.pem\fR and
+\fBpki/switchca/cacert.pem\fR that it produces will need to be copied
+over to the OpenFlow switches and controllers, respectively. Their
+contents may safely be made public.
+
+By default, \fBovs\-pki\fR generates 2048\-bit RSA keys. The \fB\-B\fR
+or \fB\-\^\-bits\fR option (see below) may be used to override the key
+length. The \fB\-k dsa\fR or \fB\-\^\-key=dsa\fR option may be used to use
+DSA in place of RSA. If DSA is selected, the \fBdsaparam.pem\fR file
+generated in the new PKI hierarchy must be copied to any machine on
+which the \fBreq\fR command (see below) will be executed. Its
+contents may safely be made public.
+
+Other files generated by \fBinit\fR may remain on the CA machine.
+The files \fBpki/controllerca/private/cakey.pem\fR and
+\fBpki/switchca/private/cakey.pem\fR have particularly sensitive
+contents that should not be exposed.
+
+.TP
+\fBreq\fR \fINAME\fR
+Generates a new private key named \fINAME\fR\fB\-privkey.pem\fR and
+corresponding certificate request named \fINAME\fR\fB\-req.pem\fR.
+The private key can be intended for use by a switch or a controller.
+
+This command should ideally be run on the switch or controller that
+will use the private key to identify itself. The file
+\fINAME\fR\fB\-req.pem\fR must be copied to the CA machine for signing
+with the \fBsign\fR command (below).
+
+This command will output a fingerprint to stdout as its final step.
+Write down the fingerprint and take it to the CA machine before
+continuing with the \fBsign\fR step.
+
+When RSA keys are in use (as is the default), \fBreq\fR, unlike the
+rest of \fBovs\-pki\fR's commands, does not need access to a PKI
+hierarchy created by \fBovs\-pki init\fR. The \fB\-B\fR or
+\fB\-\^\-bits\fR option (see below) may be used to specify the number of
+bits in the generated RSA key.
+
+When DSA keys are used (as specified with \fB\-\^\-key=dsa\fR), \fBreq\fR
+needs access to the \fBdsaparam.pem\fR file created as part of the PKI
+hierarchy (but not to other files in that tree). By default,
+\fBovs\-pki\fR looks for this file in \fB@PKIDIR@/dsaparam.pem\fR, but
+the \fB\-D\fR or \fB\-\^\-dsaparam\fR option (see below) may be used to
+specify an alternate location.
+
+\fINAME\fR\fB\-privkey.pem\fR has sensitive contents that should not be
+exposed. \fINAME\fR\fB\-req.pem\fR may be safely made public.
+
+.TP
+\fBsign\fR \fINAME\fR [\fITYPE\fR]
+Signs the certificate request named \fINAME\fR\fB\-req.pem\fR that was
+produced in the previous step, producing a certificate named
+\fINAME\fR\fB\-cert.pem\fR. \fITYPE\fR, either \fBswitch\fR (default) or
+\fBcontroller\fR, indicates the use for which the key is being
+certified.
+
+This command must be run on the CA machine.
+
+The command will output a fingerprint to stdout and request that you
+verify that it is the same fingerprint output by the \fBreq\fR
+command. This ensures that the request being signed is the same one
+produced by \fBreq\fR. (The \fB\-b\fR or \fB\-\^\-batch\fR option
+suppresses the verification step.)
+
+The file \fINAME\fR\fB\-cert.pem\fR will need to be copied back to the
+switch or controller for which it is intended. Its contents may
+safely be made public.
+
+.TP
+\fBreq+sign\fR \fINAME\fR [\fITYPE\fR]
+Combines the \fBreq\fR and \fBsign\fR commands into a single step,
+outputting all the files produced by each. The
+\fINAME\fR\fB\-privkey.pem\fR and \fINAME\fR\fB\-cert.pem\fR files must
+be copied securely to the switch or controller.
+\fINAME\fR\fB\-privkey.pem\fR has sensitive contents and must not be
+exposed in transit. Afterward, it should be deleted from the CA
+machine.
+
+This combined method is, theoretically, less secure than the
+individual steps performed separately on two different machines,
+because there is additional potential for exposure of the private
+key. However, it is also more convenient.
+
+.TP
+\fBverify\fR \fINAME\fR [\fITYPE\fR]
+Verifies that \fINAME\fR\fB\-cert.pem\fR is a valid certificate for the
+given \fITYPE\fR of use, either \fBswitch\fR (default) or
+\fBcontroller\fR. If the certificate is valid for this use, it prints
+the message ``\fINAME\fR\fB\-cert.pem\fR: OK''; otherwise, it prints an
+error message.
+
+.TP
+\fBfingerprint\fR \fIFILE\fR
+Prints the fingerprint for \fIFILE\fR. If \fIFILE\fR is a
+certificate, then this is the SHA\-1 digest of the DER encoded version
+of the certificate; otherwise, it is the SHA\-1 digest of the entire
+file.
+
+.TP
+\fBself-sign\fR \fINAME\fR
+Signs the certificate request named \fINAME\fB\-req.pem\fR using the
+private key \fINAME\fB-privkey.pem\fR, producing a self-signed
+certificate named \fINAMEfB\-cert.pem\fR. The input files should have
+been produced with \fBovs\-pki req\fR.
+
+Some controllers accept such self-signed certificates.
+
+.SH "ONLINE COMMANDS"
+
+An OpenFlow PKI can be administered online, in conjunction with
+.BR ovs\-pki\-cgi (8)
+and a web server such as Apache:
+
+.IP \(bu
+The web server exports the contents of the PKI via HTTP. All files in
+a PKI hierarchy files may be made public, except for the files
+\fBpki/controllerca/private/cakey.pem\fR and
+\fBpki/switchca/private/cakey.pem\fR, which must not be exposed.
+
+.IP \(bu
+\fBovs\-pki\-cgi\fR allows newly generated certificate requests for
+controllers and switches to be uploaded into the
+\fBpki/controllerca/incoming\fR and \fBpki/switchca/incoming\fR
+directories, respectively. Uploaded certificate requests are stored
+in those directories under names of the form
+\fIFINGERPRINT\fB\-req.pem\fR, which \fIFINGERPRINT\fR is the SHA\-1
+hash of the file.
+
+.IP \(bu
+These \fBovs\-pki\fR commands allow incoming certificate requests to
+be approved or rejected, in a form are suitable for use by humans or
+other software.
+
+.PP
+The following \fBovs\-pki\fR commands support online administration:
+
+.TP
+\fBovs\-pki\fR \fBls\fR [\fIPREFIX\fR] [\fITYPE\fR]
+Lists all of the incoming certificate requests of the given \fITYPE\fR
+(either \fBswitch\fR, the default, or \fBcontroller\fR). If
+\fIPREFIX\fR, which must be at least 4 characters long, is specified,
+it causes the list to be limited to files whose names begin with
+\fIPREFIX\fR. This is useful, for example, to avoid typing in an
+entire fingerprint when checking that a specific certificate request
+has been received.
+
+.TP
+\fBovs\-pki\fR \fBflush\fR [\fITYPE\fR]
+Deletes all certificate requests of the given \fITYPE\fR.
+
+.TP
+\fBovs\-pki\fR \fBreject\fR \fIPREFIX\fR [\fITYPE\fR]
+Rejects the certificate request whose name begins with \fIPREFIX\fR,
+which must be at least 4 characters long, of the given type (either
+\fBswitch\fR, the default, or \fBcontroller\fR). \fIPREFIX\fR must
+match exactly one certificate request; its purpose is to allow the
+user to type fewer characters, not to match multiple certificate
+requests.
+
+.TP
+\fBovs\-pki\fR \fBapprove\fR \fIPREFIX\fR [\fITYPE\fR]
+Approves the certificate request whose name begins with \fIPREFIX\fR,
+which must be at least 4 characters long, of the given \fITYPE\fR
+(either \fBswitch\fR, the default, or \fBcontroller\fR). \fIPREFIX\fR
+must match exactly one certificate request; its purpose is to allow
+the user to type fewer characters, not to match multiple certificate
+requests.
+
+The command will output a fingerprint to stdout and request that you
+verify that it is correct. (The \fB\-b\fR or \fB\-\^\-batch\fR option
+suppresses the verification step.)
+
+.TP
+\fBovs\-pki\fR \fBprompt\fR [\fITYPE\fR]
+Prompts the user for each incoming certificate request of the given
+\fITYPE\fR (either \fBswitch\fR, the default, or \fBcontroller\fR).
+Based on the certificate request's fingerprint, the user is given the
+option of approving, rejecting, or skipping the certificate request.
+
+.TP
+\fBovs\-pki\fR \fBexpire\fR [\fIAGE\fR]
+
+Rejects all the incoming certificate requests, of either type, that is
+older than \fIAGE\fR, which must in one of the forms \fIN\fBs\fR,
+\fIN\fBmin\fR, \fIN\fBh\fR, \fIN\fBday\fR. The default is \fB1day\fR.
+
+.SH OPTIONS
+.TP
+\fB\-k\fR \fItype\fR | \fB\-\^\-key=\fItype\fR
+For the \fBinit\fR command, sets the public key algorithm to use for
+the new PKI hierarchy. For the \fBreq\fR and \fBreq+sign\fR commands,
+sets the public key algorithm to use for the key to be generated,
+which must match the value specified on \fBinit\fR. With other
+commands, the value has no effect.
+
+The \fItype\fR may be \fBrsa\fR (the default) or \fBdsa\fR.
+
+.TP
+\fB\-B\fR \fInbits\fR | \fB\-\^\-bits=\fInbits\fR
+Sets the number of bits in the key to be generated. When RSA keys are
+in use, this option affects only the \fBinit\fR, \fBreq\fR, and
+\fBreq+sign\fR commands, and the same value should be given each time.
+With DSA keys are in use, this option affects only the \fBinit\fR
+command.
+
+The value must be at least 1024. The default is 2048.
+
+.TP
+\fB\-D\fR \fIfile\fR | \fB\-\^\-dsaparam=\fIfile\fR
+Specifies an alternate location for the \fBdsaparam.pem\fR file
+required by the \fBreq\fR and \fBreq+sign\fR commands. This option
+affects only these commands, and only when DSA keys are used.
+
+The default is \fBdsaparam.pem\fR under the PKI hierarchy.
+
+.TP
+\fB\-b\fR | \fB\-\^\-batch\fR
+Suppresses the interactive verification of fingerprints that the
+\fBsign\fR and \fBapprove\fR commands by default require.
+
+.TP
+\fB\-d\fR \fIdir\fR | \fB\-\^\-dir=\fR\fIdir\fR
+Specifies the location of the PKI hierarchy to be used or created by
+the command (default: \fB@PKIDIR@\fR). All commands, except \fBreq\fR,
+need access to a PKI hierarchy.
+
+.TP
+\fB\-f\fR | \fB\-\^\-force\fR
+By default, \fBovs\-pki\fR will not overwrite existing files or
+directories. This option overrides this behavior.
+
+.TP
+\fB\-l\fR \fIfile\fR | \fB\-\^\-log=\fIfile\fR
+Sets the log file to \fIfile\fR. Default:
+\fB@LOGDIR@/ovs\-pki.log\fR.
+
+.TP
+\fB\-h\fR | \fB\-\^\-help\fR
+Prints a help usage message and exits.
+
+.SH "SEE ALSO"
+
+.BR controller (8),
+.BR ovs\-pki\-cgi (8),
+.BR secchan (8)
diff --git a/utilities/ovs-pki.in b/utilities/ovs-pki.in
new file mode 100755
index 000000000..15ac17b92
--- /dev/null
+++ b/utilities/ovs-pki.in
@@ -0,0 +1,582 @@
+#! /bin/sh
+
+set -e
+
+pkidir='@PKIDIR@'
+command=
+prev=
+force=no
+batch=no
+log='@LOGDIR@/ovs-pki.log'
+keytype=rsa
+bits=2048
+for option; do
+ # This option-parsing mechanism borrowed from a Autoconf-generated
+ # configure script under the following license:
+
+ # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+ # 2002, 2003, 2004, 2005, 2006, 2009 Free Software Foundation, Inc.
+ # This configure script is free software; the Free Software Foundation
+ # gives unlimited permission to copy, distribute and modify it.
+
+ # If the previous option needs an argument, assign it.
+ if test -n "$prev"; then
+ eval $prev=\$option
+ prev=
+ continue
+ fi
+ case $option in
+ *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;;
+ *) optarg=yes ;;
+ esac
+
+ case $dashdash$option in
+ --)
+ dashdash=yes ;;
+ -h|--help)
+ cat <<EOF
+ovs-pki, for managing a simple OpenFlow public key infrastructure
+usage: $0 [OPTION...] COMMAND [ARG...]
+
+The valid stand-alone commands and their arguments are:
+ init Initialize the PKI
+ req NAME Create new private key and certificate request
+ named NAME-privkey.pem and NAME-req.pem, resp.
+ sign NAME [TYPE] Sign switch certificate request NAME-req.pem,
+ producing certificate NAME-cert.pem
+ req+sign NAME [TYPE] Combine the above two steps, producing all three files.
+ verify NAME [TYPE] Checks that NAME-cert.pem is a valid TYPE certificate
+ fingerprint FILE Prints the fingerprint for FILE
+ self-sign NAME Sign NAME-req.pem with NAME-privkey.pem,
+ producing self-signed certificate NAME-cert.pem
+
+The following additional commands manage an online PKI:
+ ls [PREFIX] [TYPE] Lists incoming requests of the given TYPE, optionally
+ limited to those whose fingerprint begins with PREFIX
+ flush [TYPE] Rejects all incoming requests of the given TYPE
+ reject PREFIX [TYPE] Rejects the incoming request(s) whose fingerprint begins
+ with PREFIX and has the given TYPE
+ approve PREFIX [TYPE] Approves the incoming request whose fingerprint begins
+ with PREFIX and has the given TYPE
+ expire [AGE] Rejects all incoming requests older than AGE, in
+ one of the forms Ns, Nmin, Nh, Nday (default: 1day)
+ prompt [TYPE] Interactively prompts to accept or reject each incoming
+ request of the given TYPE
+
+Each TYPE above is a certificate type: 'switch' (default) or 'controller'.
+
+Options for 'init', 'req', and 'req+sign' only:
+ -k, --key=rsa|dsa Type of keys to use (default: rsa)
+ -B, --bits=NBITS Number of bits in keys (default: 2048). For DSA keys,
+ this has an effect only on 'init'.
+ -D, --dsaparam=FILE File with DSA parameters (DSA only)
+ (default: dsaparam.pem within PKI directory)
+Options for use with the 'sign' and 'approve' commands:
+ -b, --batch Skip fingerprint verification
+Options that apply to any command:
+ -d, --dir=DIR Directory where the PKI is located
+ (default: $pkidir)
+ -f, --force Continue even if file or directory already exists
+ -l, --log=FILE Log openssl output to FILE (default: ovs-log.log)
+ -h, --help Print this usage message.
+EOF
+ exit 0
+ ;;
+ --di*=*)
+ pkidir=$optarg
+ ;;
+ --di*|-d)
+ prev=pkidir
+ ;;
+ --k*=*)
+ keytype=$optarg
+ ;;
+ --k*|-k)
+ prev=keytype
+ ;;
+ --bi*=*)
+ bits=$optarg
+ ;;
+ --bi*|-B)
+ prev=bits
+ ;;
+ --ds*=*)
+ dsaparam=$optarg
+ ;;
+ --ds*|-D)
+ prev=dsaparam
+ ;;
+ --l*=*)
+ log=$optarg
+ ;;
+ --l*|-l)
+ prev=log
+ ;;
+ --force|-f)
+ force=yes
+ ;;
+ --ba*|-b)
+ batch=yes
+ ;;
+ -*)
+ echo "unrecognized option $option" >&2
+ exit 1
+ ;;
+ *)
+ if test -z "$command"; then
+ command=$option
+ elif test -z "${arg1+set}"; then
+ arg1=$option
+ elif test -z "${arg2+set}"; then
+ arg2=$option
+ else
+ echo "$option: only two arguments may be specified" >&2
+ exit 1
+ fi
+ ;;
+ esac
+ shift
+done
+if test -n "$prev"; then
+ option=--`echo $prev | sed 's/_/-/g'`
+ { echo "$as_me: error: missing argument to $option" >&2
+ { (exit 1); exit 1; }; }
+fi
+if test -z "$command"; then
+ echo "$0: missing command name; use --help for help" >&2
+ exit 1
+fi
+if test "$keytype" != rsa && test "$keytype" != dsa; then
+ echo "$0: argument to -k or --key must be rsa or dsa"
+ exit 1
+fi
+if test "$bits" -lt 1024; then
+ echo "$0: argument to -B or --bits must be at least 1024"
+ exit 1
+fi
+if test -z "$dsaparam"; then
+ dsaparam=$pkidir/dsaparam.pem
+fi
+case $log in
+ /*) ;;
+ *) $log="$PWD/$log" ;;
+esac
+
+if test "$command" = "init"; then
+ if test -e "$pkidir" && test "$force" != "yes"; then
+ echo "$0: $pkidir already exists and --force not specified" >&2
+ exit 1
+ fi
+
+ if test ! -d "$pkidir"; then
+ mkdir -p "$pkidir"
+ fi
+ cd "$pkidir"
+ exec 3>>$log
+
+ if test $keytype = dsa && test ! -e dsaparam.pem; then
+ echo "Generating DSA parameters, please wait..." >&2
+ openssl dsaparam -out dsaparam.pem $bits 1>&3 2>&3
+ fi
+
+ # Create the CAs.
+ for ca in controllerca switchca; do
+ echo "Creating $ca..." >&2
+ oldpwd=$PWD
+ mkdir -p $ca
+ cd $ca
+
+ mkdir -p certs crl newcerts
+ mkdir -p -m 0700 private
+ mkdir -p -m 0733 incoming
+ touch index.txt
+ test -e crlnumber || echo 01 > crlnumber
+ test -e serial || echo 01 > serial
+
+ # Put DSA parameters in directory.
+ if test $keytype = dsa && test ! -e dsaparam.pem; then
+ cp ../dsaparam.pem .
+ fi
+
+ # Write CA configuration file.
+ if test ! -e ca.cnf; then
+ sed "s/@ca@/$ca/g" > ca.cnf <<'EOF'
+[ req ]
+prompt = no
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
+C = US
+ST = CA
+L = Palo Alto
+O = Open vSwitch
+OU = @ca@
+CN = Open vSwitch @ca@ CA Certificate
+
+[ ca ]
+default_ca = the_ca
+
+[ the_ca ]
+dir = . # top dir
+database = $dir/index.txt # index file.
+new_certs_dir = $dir/newcerts # new certs dir
+certificate = $dir/cacert.pem # The CA cert
+serial = $dir/serial # serial no file
+private_key = $dir/private/cakey.pem# CA private key
+RANDFILE = $dir/private/.rand # random number file
+default_days = 365 # how long to certify for
+default_crl_days= 30 # how long before next CRL
+default_md = md5 # md to use
+policy = policy # default policy
+email_in_dn = no # Don't add the email into cert DN
+name_opt = ca_default # Subject name display option
+cert_opt = ca_default # Certificate display option
+copy_extensions = none # Don't copy extensions from request
+
+# For the CA policy
+[ policy ]
+countryName = optional
+stateOrProvinceName = optional
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+EOF
+ fi
+
+ # Create certificate authority.
+ if test $keytype = dsa; then
+ newkey=dsa:dsaparam.pem
+ else
+ newkey=rsa:$bits
+ fi
+ openssl req -config ca.cnf -nodes \
+ -newkey $newkey -keyout private/cakey.pem -out careq.pem \
+ 1>&3 2>&3
+ openssl ca -config ca.cnf -create_serial -out cacert.pem \
+ -days 1095 -batch -keyfile private/cakey.pem -selfsign \
+ -infiles careq.pem 1>&3 2>&3
+ chmod 0700 private/cakey.pem
+
+ cd "$oldpwd"
+ done
+ exit 0
+fi
+
+one_arg() {
+ if test -z "$arg1" || test -n "$arg2"; then
+ echo "$0: $command must have exactly one argument; use --help for help" >&2
+ exit 1
+ fi
+}
+
+zero_or_one_args() {
+ if test -n "$arg2"; then
+ echo "$0: $command must have zero or one arguments; use --help for help" >&2
+ exit 1
+ fi
+}
+
+one_or_two_args() {
+ if test -z "$arg1"; then
+ echo "$0: $command must have one or two arguments; use --help for help" >&2
+ exit 1
+ fi
+}
+
+must_not_exist() {
+ if test -e "$1" && test "$force" != "yes"; then
+ echo "$0: $1 already exists and --force not supplied" >&2
+ exit 1
+ fi
+}
+
+resolve_prefix() {
+ test -n "$type" || exit 123 # Forgot to call check_type?
+
+ case $1 in
+ ????*)
+ ;;
+ *)
+ echo "Prefix $arg1 is too short (less than 4 hex digits)"
+ exit 0
+ ;;
+ esac
+
+ fingerprint=$(cd "$pkidir/${type}ca/incoming" && echo "$1"*-req.pem | sed 's/-req\.pem$//')
+ case $fingerprint in
+ "${1}*")
+ echo "No certificate requests matching $1"
+ exit 1
+ ;;
+ *" "*)
+ echo "$1 matches more than one certificate request:"
+ echo $fingerprint | sed 's/ /\
+/g'
+ exit 1
+ ;;
+ *)
+ # Nothing to do.
+ ;;
+ esac
+ req="$pkidir/${type}ca/incoming/$fingerprint-req.pem"
+ cert="$pkidir/${type}ca/certs/$fingerprint-cert.pem"
+}
+
+make_tmpdir() {
+ TMP=/tmp/ovs-pki.tmp$$
+ rm -rf $TMP
+ trap "rm -rf $TMP" 0
+ mkdir -m 0700 $TMP
+}
+
+fingerprint() {
+ local file=$1
+ local name=${1-$2}
+ local date=$(date -r $file)
+ local fingerprint
+ if grep -q -e '-BEGIN CERTIFICATE-' "$file"; then
+ fingerprint=$(openssl x509 -noout -in "$file" -fingerprint |
+ sed 's/SHA1 Fingerprint=//' | tr -d ':')
+ else
+ fingerprint=$(sha1sum "$file" | awk '{print $1}')
+ fi
+ printf "$name\\t$date\\n"
+ case $file in
+ $fingerprint*)
+ printf "\\t(correct fingerprint in filename)\\n"
+ ;;
+ *)
+ printf "\\tfingerprint $fingerprint\\n"
+ ;;
+ esac
+}
+
+verify_fingerprint() {
+ fingerprint "$@"
+ if test $batch != yes; then
+ echo "Does fingerprint match? (yes/no)"
+ read answer
+ if test "$answer" != yes; then
+ echo "Match failure, aborting" >&2
+ exit 1
+ fi
+ fi
+}
+
+check_type() {
+ if test x = x"$1"; then
+ type=switch
+ elif test "$1" = switch || test "$1" = controller; then
+ type=$1
+ else
+ echo "$0: type argument must be 'switch' or 'controller'" >&2
+ exit 1
+ fi
+}
+
+parse_age() {
+ number=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\1/')
+ unit=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\2/')
+ case $unit in
+ s)
+ factor=1
+ ;;
+ min)
+ factor=60
+ ;;
+ h)
+ factor=3600
+ ;;
+ day)
+ factor=86400
+ ;;
+ *)
+ echo "$1: age not in the form Ns, Nmin, Nh, Nday (e.g. 1day)" >&2
+ exit 1
+ ;;
+ esac
+ echo $(($number * $factor))
+}
+
+must_exist() {
+ if test ! -e "$1"; then
+ echo "$0: $1 does not exist" >&2
+ exit 1
+ fi
+}
+
+pkidir_must_exist() {
+ if test ! -e "$pkidir"; then
+ echo "$0: $pkidir does not exist (need to run 'init' or use '--dir'?)" >&2
+ exit 1
+ elif test ! -d "$pkidir"; then
+ echo "$0: $pkidir is not a directory" >&2
+ exit 1
+ fi
+}
+
+make_request() {
+ must_not_exist "$arg1-privkey.pem"
+ must_not_exist "$arg1-req.pem"
+ make_tmpdir
+ cat > "$TMP/req.cnf" <<EOF
+[ req ]
+prompt = no
+distinguished_name = req_distinguished_name
+
+[ req_distinguished_name ]
+C = US
+ST = CA
+L = Palo Alto
+O = Open vSwitch
+OU = Open vSwitch certifier
+CN = Open vSwitch certificate for $arg1
+EOF
+ if test $keytype = rsa; then
+ newkey=rsa:$bits
+ else
+ must_exist "$dsaparam"
+ newkey=dsa:$dsaparam
+ fi
+ openssl req -config "$TMP/req.cnf" -text -nodes \
+ -newkey $newkey -keyout "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3
+}
+
+sign_request() {
+ must_exist "$1"
+ must_not_exist "$2"
+ pkidir_must_exist
+
+ (cd "$pkidir/${type}ca" &&
+ openssl ca -config ca.cnf -batch -in /dev/stdin) \
+ < "$1" > "$2.tmp$$" 2>&3
+ mv "$2.tmp$$" "$2"
+}
+
+glob() {
+ local files=$(echo $1)
+ if test "$files" != "$1"; then
+ echo "$files"
+ fi
+}
+
+exec 3>>$log || true
+if test "$command" = req; then
+ one_arg
+
+ make_request "$arg1"
+ fingerprint "$arg1-req.pem"
+elif test "$command" = sign; then
+ one_or_two_args
+ check_type "$arg2"
+ verify_fingerprint "$arg1-req.pem"
+
+ sign_request "$arg1-req.pem" "$arg2-cert.pem"
+elif test "$command" = req+sign; then
+ one_or_two_args
+ check_type "$arg2"
+
+ pkidir_must_exist
+ make_request "$arg1"
+ sign_request "$arg1-req.pem" "$arg1-cert.pem"
+ fingerprint "$arg1-req.pem"
+elif test "$command" = verify; then
+ one_or_two_args
+ must_exist "$arg1-cert.pem"
+ check_type "$arg2"
+
+ pkidir_must_exist
+ openssl verify -CAfile "$pkidir/${type}ca/cacert.pem" "$arg1-cert.pem"
+elif test "$command" = fingerprint; then
+ one_arg
+
+ fingerprint "$arg1"
+elif test "$command" = self-sign; then
+ one_arg
+ must_exist "$arg1-req.pem"
+ must_exist "$arg1-privkey.pem"
+ must_not_exist "$arg1-cert.pem"
+
+ openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem" \
+ -signkey "$arg1-privkey.pem" -req -text 2>&3
+elif test "$command" = ls; then
+ check_type "$arg2"
+
+ cd "$pkidir/${type}ca/incoming"
+ for file in $(glob "$arg1*-req.pem"); do
+ fingerprint $file
+ done
+elif test "$command" = flush; then
+ check_type "$arg1"
+
+ rm -f "$pkidir/${type}ca/incoming/"*
+elif test "$command" = reject; then
+ one_or_two_args
+ check_type "$arg2"
+ resolve_prefix "$arg1"
+
+ rm -f "$req"
+elif test "$command" = approve; then
+ one_or_two_args
+ check_type "$arg2"
+ resolve_prefix "$arg1"
+
+ make_tmpdir
+ cp "$req" "$TMP/$req"
+ verify_fingerprint "$TMP/$req"
+ sign_request "$TMP/$req"
+ rm -f "$req" "$TMP/$req"
+elif test "$command" = prompt; then
+ zero_or_one_args
+ check_type "$arg1"
+
+ make_tmpdir
+ cd "$pkidir/${type}ca/incoming"
+ for req in $(glob "*-req.pem"); do
+ cp "$req" "$TMP/$req"
+
+ cert=$(echo "$pkidir/${type}ca/certs/$req" |
+ sed 's/-req.pem/-cert.pem/')
+ if test -f $cert; then
+ echo "Request $req already approved--dropping duplicate request"
+ rm -f "$req" "$TMP/$req"
+ continue
+ fi
+
+ echo
+ echo
+ fingerprint "$TMP/$req" "$req"
+ printf "Disposition for this request (skip/approve/reject)? "
+ read answer
+ case $answer in
+ approve)
+ echo "Approving $req"
+ sign_request "$TMP/$req" "$cert"
+ rm -f "$req" "$TMP/$req"
+ ;;
+ r*)
+ echo "Rejecting $req"
+ rm -f "$req" "$TMP/$req"
+ ;;
+ *)
+ echo "Skipping $req"
+ ;;
+ esac
+ done
+elif test "$command" = expire; then
+ zero_or_one_args
+ cutoff=$(($(date +%s) - $(parse_age ${arg1-1day})))
+ for type in switch controller; do
+ cd "$pkidir/${type}ca/incoming" || exit 1
+ for file in $(glob "*"); do
+ time=$(date -r "$file" +%s)
+ if test "$time" -lt "$cutoff"; then
+ rm -f "$file"
+ fi
+ done
+ done
+else
+ echo "$0: $command command unknown; use --help for help" >&2
+ exit 1
+fi
diff --git a/utilities/ovs-wdt.c b/utilities/ovs-wdt.c
new file mode 100644
index 000000000..3c5d797c0
--- /dev/null
+++ b/utilities/ovs-wdt.c
@@ -0,0 +1,263 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+
+/* Default values for the interval and timer. In seconds. */
+#define DEFAULT_INTERVAL 1
+#define DEFAULT_TIMEOUT 30
+
+int fd = -1;
+
+/* The WDT is automatically enabled when /dev/watchdog is opened. If we
+ * do not send the magic value to the device first before exiting, the
+ * system will reboot. This function allows the program to exit without
+ * causing a reboot.
+ */
+static void
+cleanup(void)
+{
+ if (fd == -1) {
+ return;
+ }
+
+ /* Writing the magic value "V" to the device is an indication that
+ * the device is about to be closed. This causes the watchdog to be
+ * disabled after the call to close.
+ */
+ if (write(fd, "V", 1) != 1) {
+ fprintf(stderr, "Couldn't write magic val: %d\n", errno);
+ return;
+ }
+ close(fd);
+ fd = -1;
+}
+
+
+/* If we receive a SIGINT, cleanup first, which will disable the
+ * watchdog timer.
+ */
+static void
+sighandler(int signum)
+{
+ cleanup();
+ signal(signum, SIG_DFL);
+ raise(signum);
+}
+
+static void
+setup_signal(void)
+{
+ struct sigaction action;
+
+ action.sa_handler = sighandler;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+
+ if (sigaction(SIGINT, &action, NULL) != 0) {
+ fprintf(stderr, "Problem setting up SIGINT handler...\n");
+ }
+ if (sigaction(SIGTERM, &action, NULL) != 0) {
+ fprintf(stderr, "Problem setting up SIGTERM handler...\n");
+ }
+}
+
+
+/* Print information on the WDT hardware */
+static void
+print_wdt_info(void)
+{
+ struct watchdog_info ident;
+
+ if (ioctl(fd, WDIOC_GETSUPPORT, &ident) == -1) {
+ fprintf(stderr, "Couldn't get version: %d\n", errno);
+ cleanup();
+ exit(-1);
+ }
+ printf("identity: %s, ver: %d, opt: %#x\n", ident.identity,
+ ident.firmware_version, ident.options);
+}
+
+
+static void
+print_help(char *progname)
+{
+ printf("%s: Watchdog timer utility\n", progname);
+ printf("usage: %s [OPTIONS]\n\n", progname);
+ printf("Options:\n");
+ printf(" -t, --timeout=SECS expiration time of WDT (default: %d)\n",
+ DEFAULT_TIMEOUT);
+ printf(" -i, --interval=SECS interval to send keep-alives (default: %d)\n",
+ DEFAULT_INTERVAL);
+ printf(" -d, --disable disable the WDT and exit\n");
+ printf(" -h, --help display this help message\n");
+ printf(" -v, --verbose enable verbose printing\n");
+ printf(" -V, --version display version information of WDT and exit\n");
+}
+
+
+int main(int argc, char *argv[])
+{
+ int arg;
+ int optc;
+ int verbose = 0;
+ int interval = DEFAULT_INTERVAL;
+ int timeout = DEFAULT_TIMEOUT;
+ static struct option const longopts[] =
+ {
+ {"timeout", required_argument, NULL, 't'},
+ {"interval", required_argument, NULL, 'i'},
+ {"disable", no_argument, NULL, 'd'},
+ {"help", no_argument, NULL, 'h'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {0, 0, 0, 0}
+ };
+
+ setup_signal();
+
+ fd = open("/dev/watchdog", O_RDWR);
+ if (fd == -1) {
+ fprintf(stderr, "Couldn't open watchdog device: %s\n", strerror(errno));
+ exit(-1);
+ }
+
+ while ((optc = getopt_long(argc, argv, "t:i:dh?vV", longopts, NULL)) != -1) {
+ switch (optc) {
+ case 't':
+ timeout = strtol(optarg, NULL, 10);
+ if (!timeout) {
+ fprintf(stderr, "Invalid timeout: %s\n", optarg);
+ goto error;
+ }
+ break;
+
+ case 'i':
+ interval = strtol(optarg, NULL, 10);
+ if (!interval) {
+ fprintf(stderr, "Invalid interval: %s\n", optarg);
+ goto error;
+ }
+ break;
+
+ case 'd':
+ arg = WDIOS_DISABLECARD;
+ if (ioctl(fd, WDIOC_SETOPTIONS, &arg) == -1) {
+ fprintf(stderr, "Couldn't disable: %d\n", errno);
+ goto error;
+ }
+ cleanup();
+ exit(0);
+ break;
+
+ case 'h':
+ print_help(argv[0]);
+ cleanup();
+ exit(0);
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case 'V':
+ print_wdt_info();
+ cleanup();
+ exit(0);
+ break;
+
+ default:
+ print_help(argv[0]);
+ goto error;
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* Sanity-check the arguments */
+ if (argc != 0) {
+ fprintf(stderr, "Illegal argument: %s\n", argv[0]);
+ goto error;
+ }
+
+ if (verbose) {
+ print_wdt_info();
+ printf("timeout: %d, interval: %d\n", timeout, interval);
+ }
+
+ /* Prevent the interval being greater than the timeout, since it
+ * will always cause a reboot.
+ */
+ if (interval > timeout) {
+ fprintf(stderr, "Interval greater than timeout: %d > %d\n",
+ interval, timeout);
+ goto error;
+ }
+
+ /* Always set the timeout */
+ if (ioctl(fd, WDIOC_SETTIMEOUT, &timeout) == -1) {
+ fprintf(stderr, "Couldn't set timeout: %d\n", errno);
+ goto error;
+ }
+
+ /* Loop and send a keep-alive every "interval" seconds */
+ while (1) {
+ if (verbose) {
+ if (ioctl(fd, WDIOC_GETTIMELEFT, &arg) == -1) {
+ fprintf(stderr, "Couldn't get time left: %d\n", errno);
+ goto error;
+ }
+ printf("Sending keep alive, time remaining: %d\n", arg);
+ }
+
+ /* Send a keep-alive. The argument is ignored */
+ if (ioctl(fd, WDIOC_KEEPALIVE, &arg) == -1) {
+ fprintf(stderr, "Couldn't keepalive: %d\n", errno);
+ goto error;
+ }
+
+ sleep(interval);
+ }
+
+ /* Never directly reached... */
+error:
+ cleanup();
+ exit(-1);
+}