TESTS_ENVIRONMENT =
bin_PROGRAMS =
bin_SCRIPTS =
+dist_commands_DATA =
dist_man_MANS =
man_MANS =
noinst_HEADERS =
do_subst = ($(srcdir)/subst VLOG_OPTIONS $(srcdir)/lib/vlog.man | \
sed -e 's,[@]PKIDIR[@],$(PKIDIR),g' \
-e 's,[@]RUNDIR[@],$(RUNDIR),g' \
+ -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
-e 's,[@]PERL[@],$(PERL),g')
ro_script = sed "`printf '1a\\' && printf '\\n\# -*- buffer-read-only: t -*-'`"
ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */'
OFP_CHECK_IF_PACKET
OFP_CHECK_HWTABLES
+AC_CHECK_FUNCS([strsignal])
+
AC_ARG_VAR(KARCH, [Kernel Architecture String])
AC_SUBST(KARCH)
OFP_CHECK_LINUX(l26, 2.6, 2.6, KSRC26, L26_ENABLED)
# use openflow-switchmon.
MGMT_VCONNS="punix:/var/run/secchan.socket"
+# COMMANDS: Access control list for the commands that can be executed
+# remotely over the OpenFlow protocol, as a comma-separated list of
+# shell glob patterns. Negative patterns (beginning with !) act as a
+# blacklist. To be executable, a command name must match one positive
+# pattern and not match any negative patterns.
+#COMMANDS="reboot,update"
+
# DAEMON_OPTS: Additional options to pass to secchan, e.g. "--fail=open"
DAEMON_OPTS=""
# 'restart' will not work
# Include secchan defaults if available
+unset NETDEVS
+unset MODE
+unset SWITCH_IP
+unset CONTROLLER
+unset PRIVKEY
+unset CERT
+unset CACERT
+unset CACERT_MODE
+unset MGMT_VCONNS
+unset COMMANDS
+unset DAEMON_OPTS
default=/etc/default/openflow-switch
if [ -f $default ] ; then
. $default
MGMT_OPTS="$MGMT_OPTS --listen=$vconn"
done
+ COMMAND_OPT=
+ if test -n "$COMMANDS"; then
+ COMMAND_OPT="--commands=$COMMANDS"
+ fi
+
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --pidfile $PIDFILE \
--exec $DAEMON -- nl:0 $CONTROLLER --detach --pidfile=$PIDFILE \
- --verbose=ANY:console:emer $DAEMON_OPTS $MGMT_OPTS $SSL_OPTS
+ --verbose=ANY:console:emer $DAEMON_OPTS $MGMT_OPTS $SSL_OPTS \
+ "$COMMAND_OPT"
if running; then
echo "$NAME."
else
_debian/utilities/ofp-discover usr/sbin
_debian/utilities/ofp-kill usr/sbin
debian/ofp-switch-setup usr/sbin
+debian/openflow/usr/share/openflow/commands usr/share/openflow/commands
#ifndef DIRS_H
#define DIRS_H 1
-extern const char ofp_rundir[]; /* /usr/local/var/run */
-extern const char ofp_logdir[]; /* /usr/local/var/log */
+extern const char ofp_pkgdatadir[]; /* /usr/local/share/openflow */
+extern const char ofp_rundir[]; /* /usr/local/var/run */
+extern const char ofp_logdir[]; /* /usr/local/var/log */
#endif /* dirs.h */
NXT_ACT_SET_CONFIG,
/* Get configuration of action. */
- NXT_ACT_GET_CONFIG
+ NXT_ACT_GET_CONFIG,
+
+ /* Remote command execution. The request body is a sequence of strings
+ * delimited by null bytes. The first string is a command name.
+ * Subsequent strings are command arguments. */
+ NXT_COMMAND_REQUEST,
+
+ /* Remote command execution reply, sent when the command's execution
+ * completes. The reply body is struct nx_command_reply. */
+ NXT_COMMAND_REPLY
};
struct nicira_header {
};
OFP_ASSERT(sizeof(struct nx_action_header) == 16);
+/* Status bits for NXT_COMMAND_REPLY. */
+enum {
+ NXT_STATUS_EXITED = 0x8000, /* Exited normally. */
+ NXT_STATUS_SIGNALED = 0x4000, /* Exited due to signal. */
+ NXT_STATUS_UNKNOWN = 0x2000, /* Exited for unknown reason. */
+ NXT_STATUS_COREDUMP = 0x1000, /* Exited with core dump. */
+ NXT_STATUS_EXITSTATUS = 0xff, /* Exit code mask if NXT_STATUS_EXITED. */
+ NXT_STATUS_TERMSIG = 0xff, /* Signal number if NXT_STATUS_SIGNALED. */
+};
+
+/* NXT_COMMAND_REPLY. */
+struct nx_command_reply {
+ struct nicira_header nxh;
+ uint32_t status; /* Status bits defined above. */
+ /* Followed by any number of bytes of process output. */
+};
+OFP_ASSERT(sizeof(struct nx_command_reply) == 20);
+
#endif /* nicira-ext.h */
#include <stdbool.h>
int set_nonblocking(int fd);
+int get_max_fds(void);
int lookup_ip(const char *host_name, struct in_addr *address);
int get_socket_error(int sock);
int check_connection_completion(int fd);
VLOG_MODULE(discovery)
VLOG_MODULE(dpif)
VLOG_MODULE(dpctl)
+VLOG_MODULE(executer)
VLOG_MODULE(fail_open)
VLOG_MODULE(fault)
VLOG_MODULE(flow)
CLEANFILES += lib/dirs.c
lib/dirs.c: Makefile
($(ro_c) && \
+ echo 'const char ofp_pkgdatadir[] = "$(pkgdatadir$)";' && \
echo 'const char ofp_rundir[] = "@RUNDIR@";' && \
echo 'const char ofp_logdir[] = "@LOGDIR@";') > lib/dirs.c.tmp
mv lib/dirs.c.tmp lib/dirs.c
#include <stddef.h>
#include <stdio.h>
#include <string.h>
+#include <sys/resource.h>
#include <sys/un.h>
#include <unistd.h>
#include "fatal-signal.h"
}
}
+/* Returns the maximum valid FD value, plus 1. */
+int
+get_max_fds(void)
+{
+ static int max_fds = -1;
+ if (max_fds < 0) {
+ struct rlimit r;
+ if (!getrlimit(RLIMIT_NOFILE, &r)
+ && r.rlim_cur != RLIM_INFINITY
+ && r.rlim_cur != RLIM_SAVED_MAX
+ && r.rlim_cur != RLIM_SAVED_CUR) {
+ max_fds = r.rlim_cur;
+ } else {
+ VLOG_WARN("failed to obtain fd limit, defaulting to 1024");
+ max_fds = 1024;
+ }
+ }
+ return max_fds;
+}
+
/* Translates 'host_name', which may be a DNS name or an IP address, into a
* numeric IP address in '*addr'. Returns 0 if successful, otherwise a
* positive errno value. */
secchan_secchan_SOURCES = \
secchan/discovery.c \
secchan/discovery.h \
+ secchan/executer.c \
+ secchan/executer.h \
secchan/fail-open.c \
secchan/fail-open.h \
secchan/in-band.c \
secchan/ratelimit.c \
secchan/ratelimit.h \
secchan/secchan.c \
+ secchan/secchan.h \
secchan/status.c \
secchan/status.h \
secchan/stp-secchan.c \
EXTRA_DIST += secchan/secchan.8.in
DISTCLEANFILES += secchan/secchan.8
+
+include secchan/commands/automake.mk
--- /dev/null
+commandsdir = ${pkgdatadir}/commands
+dist_commands_SCRIPTS = \
+ secchan/commands/reboot \
+ secchan/commands/update
--- /dev/null
+#! /bin/sh
+reboot
--- /dev/null
+#! /bin/sh -e
+apt-get update -qy
+apt-get upgrade -qy
--- /dev/null
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include <config.h>
+#include "executer.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <unistd.h>
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "nicira-ext.h"
+#include "ofpbuf.h"
+#include "openflow.h"
+#include "poll-loop.h"
+#include "rconn.h"
+#include "secchan.h"
+#include "socket-util.h"
+#include "util.h"
+#include "vconn.h"
+
+#define THIS_MODULE VLM_executer
+#include "vlog.h"
+
+#define MAX_CHILDREN 8
+
+struct child {
+ /* Information about child process. */
+ char *name; /* argv[0] passed to child. */
+ pid_t pid; /* Child's process ID. */
+
+ /* For sending a reply to the controller when the child dies. */
+ struct relay *relay;
+ uint32_t xid; /* Transaction ID used by controller. */
+
+ /* We read up to MAX_OUTPUT bytes of output and send them back to the
+ * controller when the child dies. */
+#define MAX_OUTPUT 4096
+ int output_fd; /* FD from which to read child's output. */
+ uint8_t *output; /* Output data. */
+ size_t output_size; /* Number of bytes of output data so far. */
+};
+
+struct executer {
+ const struct settings *s;
+
+ /* Children. */
+ struct child children[MAX_CHILDREN];
+ size_t n_children;
+
+ /* File descriptors. */
+ int wait_fd; /* Pipe FD for wakeup when on SIGCHLD. */
+ int null_fd; /* FD for /dev/null. */
+};
+
+/* Returns true if 'cmd' is allowed by 'acl', which is a command-separated
+ * access control list in the format described for --command-acl in
+ * secchan(8). */
+static bool
+executer_is_permitted(const char *acl_, const char *cmd)
+{
+ char *acl, *save_ptr, *pattern;
+ bool allowed, denied;
+
+ /* Verify that 'cmd' consists only of alphanumerics plus _ or -. */
+ if (cmd[strspn(cmd, "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")] != '\0') {
+ VLOG_WARN("rejecting command name \"%s\" that contain forbidden "
+ "characters", cmd);
+ return false;
+ }
+
+ /* Check 'cmd' against 'acl'. */
+ acl = xstrdup(acl_);
+ save_ptr = acl;
+ allowed = denied = false;
+ while ((pattern = strsep(&save_ptr, ",")) != NULL && !denied) {
+ if (pattern[0] != '!' && !fnmatch(pattern, cmd, 0)) {
+ allowed = true;
+ } else if (pattern[0] == '!' && !fnmatch(pattern + 1, cmd, 0)) {
+ denied = true;
+ }
+ }
+ free(acl);
+
+ /* Check the command white/blacklisted state. */
+ if (allowed && !denied) {
+ VLOG_WARN("permitting command execution: \"%s\" is whitelisted", cmd);
+ } else if (allowed && denied) {
+ VLOG_WARN("denying command execution: \"%s\" is both blacklisted "
+ "and whitelisted", cmd);
+ } else if (!allowed) {
+ VLOG_WARN("denying command execution: \"%s\" is not whitelisted", cmd);
+ } else if (denied) {
+ VLOG_WARN("denying command execution: \"%s\" is blacklisted", cmd);
+ }
+ return allowed && !denied;
+}
+
+static bool
+executer_remote_packet_cb(struct relay *r, void *e_)
+{
+ struct executer *e = e_;
+ struct ofpbuf *msg = r->halves[HALF_REMOTE].rxbuf;
+ struct nicira_header *request;
+ char **argv;
+ char *args;
+ char *exec_file = NULL;
+ int max_fds;
+ struct stat s;
+ size_t args_size;
+ size_t argc;
+ size_t i;
+ pid_t pid;
+ int output_fds[2];
+
+ /* Check for NXT_COMMAND_REQUEST vendor extension. */
+ if (msg->size < sizeof(struct nicira_header)) {
+ return false;
+ }
+ request = msg->data;
+ if (request->header.type != OFPT_VENDOR
+ || request->vendor != htonl(NX_VENDOR_ID)
+ || request->subtype != htonl(NXT_COMMAND_REQUEST)) {
+ return false;
+ }
+
+ /* Verify limit on children not exceeded.
+ * XXX should probably kill children when the connection drops? */
+ if (e->n_children >= MAX_CHILDREN) {
+ VLOG_WARN("limit of %d child processes reached, dropping request",
+ MAX_CHILDREN);
+ return false;
+ }
+
+ /* Copy argument buffer, adding a null terminator at the end. Now every
+ * argument is null-terminated, instead of being merely null-delimited. */
+ args_size = msg->size - sizeof *request;
+ args = xmemdup0((const void *) (request + 1), args_size);
+
+ /* Count arguments. */
+ argc = 0;
+ for (i = 0; i <= args_size; i++) {
+ argc += args[i] == '\0';
+ }
+
+ /* Set argv[*] to point to each argument. */
+ argv = xmalloc((argc + 1) * sizeof *argv);
+ argv[0] = args;
+ for (i = 1; i < argc; i++) {
+ argv[i] = strchr(argv[i - 1], '\0') + 1;
+ }
+ argv[argc] = NULL;
+
+ /* Check permissions. */
+ if (!executer_is_permitted(e->s->command_acl, argv[0])) {
+ goto done;
+ }
+
+ /* Find the executable. */
+ exec_file = xasprintf("%s/%s", e->s->command_dir, argv[0]);
+ if (stat(exec_file, &s)) {
+ VLOG_WARN("failed to stat \"%s\": %s", exec_file, strerror(errno));
+ goto done;
+ }
+ if (!S_ISREG(s.st_mode)) {
+ VLOG_WARN("\"%s\" is not a regular file", exec_file);
+ goto done;
+ }
+ argv[0] = exec_file;
+
+ /* Arrange to capture output. */
+ if (pipe(output_fds)) {
+ VLOG_WARN("pipe failed: %s", strerror(errno));
+ goto done;
+ }
+
+ pid = fork();
+ if (!pid) {
+ /* Running in child.
+ * XXX should run in new process group so that we can signal all
+ * subprocesses at once? Would also want to catch fatal signals and
+ * kill them at the same time though. */
+ fatal_signal_fork();
+ dup2(e->null_fd, 0);
+ dup2(output_fds[1], 1);
+ dup2(e->null_fd, 2);
+ max_fds = get_max_fds();
+ for (i = 3; i < max_fds; i++) {
+ close(i);
+ }
+ if (chdir(e->s->command_dir)) {
+ printf("could not change directory to \"%s\": %s",
+ e->s->command_dir, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ execv(argv[0], argv);
+ printf("failed to start \"%s\": %s\n", argv[0], strerror(errno));
+ exit(EXIT_FAILURE);
+ } else if (pid > 0) {
+ /* Running in parent. */
+ struct child *child;
+
+ VLOG_WARN("started \"%s\" subprocess", argv[0]);
+ child = &e->children[e->n_children++];
+ child->name = xstrdup(argv[0]);
+ child->pid = pid;
+ child->relay = r;
+ child->xid = request->header.xid;
+ child->output_fd = output_fds[0];
+ child->output = xmalloc(MAX_OUTPUT);
+ child->output_size = 0;
+ set_nonblocking(output_fds[0]);
+ close(output_fds[1]);
+ } else {
+ VLOG_WARN("fork failed: %s", strerror(errno));
+ close(output_fds[0]);
+ close(output_fds[1]);
+ }
+
+done:
+ free(exec_file);
+ free(args);
+ free(argv);
+ return true;
+}
+
+/* 'child' died with 'status' as its return code. Deal with it. */
+static void
+child_terminated(struct child *child, int status)
+{
+ /* Log how it terminated. */
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ if (WIFEXITED(status)) {
+ ds_put_format(&ds, "normally with status %d", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+#ifdef HAVE_STRSIGNAL
+ ds_put_format(&ds, "by signal %s", strsignal(WTERMSIG(status)));
+#else
+ ds_put_format(&ds, "by signal %d", WTERMSIG(status));
+#endif
+ }
+ if (WCOREDUMP(status)) {
+ ds_put_cstr(&ds, " (core dumped)");
+ }
+ VLOG_WARN("child process \"%s\" with pid %ld terminated %s",
+ child->name, (long int) child->pid, ds_cstr(&ds));
+ ds_destroy(&ds);
+
+ /* Send a status message back to the controller that requested the
+ * command. */
+ if (child->relay) {
+ struct nx_command_reply *r;
+ struct ofpbuf *buffer;
+
+ r = make_openflow_xid(sizeof *r, OFPT_VENDOR, child->xid, &buffer);
+ r->nxh.vendor = htonl(NX_VENDOR_ID);
+ r->nxh.subtype = htonl(NXT_COMMAND_REPLY);
+ if (WIFEXITED(status)) {
+ r->status = htonl(WEXITSTATUS(status) | NXT_STATUS_EXITED);
+ } else if (WIFSIGNALED(status)) {
+ r->status = htonl(WTERMSIG(status) | NXT_STATUS_SIGNALED);
+ } else {
+ r->status = htonl(NXT_STATUS_UNKNOWN);
+ }
+ if (WCOREDUMP(status)) {
+ r->status |= htonl(NXT_STATUS_COREDUMP);
+ }
+ ofpbuf_put(buffer, child->output, child->output_size);
+ update_openflow_length(buffer);
+ if (rconn_send(child->relay->halves[HALF_REMOTE].rconn, buffer,
+ NULL)) {
+ ofpbuf_delete(buffer);
+ }
+ }
+}
+
+/* Read output from 'child' and append it to its output buffer. */
+static void
+poll_child(struct child *child)
+{
+ ssize_t n;
+
+ if (child->output_fd < 0) {
+ return;
+ }
+
+ do {
+ n = read(child->output_fd, child->output + child->output_size,
+ MAX_OUTPUT - child->output_size);
+ } while (n < 0 && errno == EINTR);
+ if (n > 0) {
+ child->output_size += n;
+ if (child->output_size < MAX_OUTPUT) {
+ return;
+ }
+ } else if (n < 0 && errno == EAGAIN) {
+ return;
+ }
+ close(child->output_fd);
+ child->output_fd = -1;
+}
+
+static void
+executer_periodic_cb(void *e_)
+{
+ struct executer *e = e_;
+ char buffer[MAX_CHILDREN];
+ size_t i;
+
+ if (!e->n_children) {
+ return;
+ }
+
+ /* Read output from children. */
+ for (i = 0; i < e->n_children; i++) {
+ struct child *child = &e->children[i];
+ poll_child(child);
+ }
+
+ /* If SIGCHLD was received, reap dead children. */
+ if (read(e->wait_fd, buffer, sizeof buffer) <= 0) {
+ return;
+ }
+ for (;;) {
+ int status;
+ pid_t pid;
+
+ /* Get dead child in 'pid' and its return code in 'status'. */
+ pid = waitpid(WAIT_ANY, &status, WNOHANG);
+ if (pid < 0 && errno == EINTR) {
+ continue;
+ } else if (pid <= 0) {
+ return;
+ }
+
+ /* Find child with given 'pid' and drop it from the list. */
+ for (i = 0; i < e->n_children; i++) {
+ struct child *child = &e->children[i];
+ if (child->pid == pid) {
+ poll_child(child);
+ child_terminated(child, status);
+ free(child->name);
+ free(child->output);
+ *child = e->children[--e->n_children];
+ goto found;
+ }
+ }
+ VLOG_WARN("child with unknown pid %ld terminated", (long int) pid);
+ found:;
+ }
+
+}
+
+static void
+executer_wait_cb(void *e_)
+{
+ struct executer *e = e_;
+ if (e->n_children) {
+ size_t i;
+
+ /* Wake up on SIGCHLD. */
+ poll_fd_wait(e->wait_fd, POLLIN);
+
+ /* Wake up when we get output from a child. */
+ for (i = 0; i < e->n_children; i++) {
+ struct child *child = &e->children[i];
+ if (child->output_fd >= 0) {
+ poll_fd_wait(e->wait_fd, POLLIN);
+ }
+ }
+ }
+}
+
+static void
+executer_closing_cb(struct relay *r, void *e_)
+{
+ struct executer *e = e_;
+ size_t i;
+
+ /* If any of our children was connected to 'r', then disconnect it so we
+ * don't try to reference a dead connection when the process terminates
+ * later.
+ * XXX kill the children started by 'r'? */
+ for (i = 0; i < e->n_children; i++) {
+ if (e->children[i].relay == r) {
+ e->children[i].relay = NULL;
+ }
+ }
+}
+
+static int child_fd;
+
+static void
+sigchld_handler(int signr UNUSED)
+{
+ write(child_fd, "", 1);
+}
+
+static struct hook_class executer_hook_class = {
+ NULL, /* local_packet_cb */
+ executer_remote_packet_cb, /* remote_packet_cb */
+ executer_periodic_cb, /* periodic_cb */
+ executer_wait_cb, /* wait_cb */
+ executer_closing_cb, /* closing_cb */
+};
+
+void
+executer_start(struct secchan *secchan, const struct settings *settings)
+{
+ struct executer *e;
+ struct sigaction sa;
+ int fds[2], null_fd;
+
+ /* Create pipe for notifying us that SIGCHLD was invoked. */
+ if (pipe(fds)) {
+ ofp_fatal(errno, "pipe failed");
+ }
+ set_nonblocking(fds[0]);
+ set_nonblocking(fds[1]);
+ child_fd = fds[1];
+
+ /* Open /dev/null. */
+ null_fd = open("/dev/null", O_RDWR);
+ if (null_fd < 0) {
+ ofp_fatal(errno, "could not open /dev/null");
+ }
+
+ /* Set up signal handler. */
+ memset(&sa, 0, sizeof sa);
+ sa.sa_handler = sigchld_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
+ if (sigaction(SIGCHLD, &sa, NULL)) {
+ ofp_fatal(errno, "sigaction(SIGCHLD) faile");
+ }
+
+ /* Add hook. */
+ e = xcalloc(1, sizeof *e);
+ e->s = settings;
+ e->n_children = 0;
+ e->wait_fd = fds[0];
+ e->null_fd = null_fd;
+ add_hook(secchan, &executer_hook_class, e);
+}
--- /dev/null
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#ifndef EXECUTER_H
+#define EXECUTER_H 1
+
+struct secchan;
+struct settings;
+
+void executer_start(struct secchan *, const struct settings *);
+
+#endif /* executer.h */
because bugs in the STP implementation are still being worked out.
The default will change to \fB--stp\fR at some point in the future.
+.TP
+\fB--command-acl=\fR[\fB!\fR]\fIglob\fR[\fB,\fR[\fB!\fR]\fIglob\fR...]
+Configures the commands that remote OpenFlow connections are allowed
+to invoke using (e.g.) \fBdpctl execute\fR. The argument is a
+comma-separated sequence of shell glob patterns. A glob pattern
+specified without a leading \fB!\fR is a ``whitelist'' that specifies
+a set of commands that are that may be invoked, whereas a pattern that
+does begin with \fB!\fR is a ``blacklist'' that specifies commands
+that may not be invoked. To be permitted, a command name must be
+whitelisted and must not be blacklisted;
+e.g. \fB--command-acl=up*,!upgrade\fR would allow any command whose name
+begins with \fBup\fR except for the command named \fBupgrade\fR.
+Command names that include characters other than upper- and lower-case
+English letters, digits, and the underscore and hyphen characters are
+unconditionally disallowed.
+
+When the whitelist and blacklist permit a command name, \fBsecchan\fR
+looks for a program with the same name as the command in the commands
+directory (see below). Other directories are not searched.
+
+.TP
+\fB--command-dir=\fIdirectory\fR
+Sets the directory searched for remote command execution to
+\fBdirectory\fR. The default directory is
+\fB@pkgdatadir@/commands\fR.
+
.TP
\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
Specifies a PEM file containing the private key used as the switch's
#include "command-line.h"
#include "compiler.h"
#include "daemon.h"
+#include "dirs.h"
#include "discovery.h"
+#include "executer.h"
#include "fail-open.h"
#include "fault.h"
#include "in-band.h"
rate_limit_start(&secchan, &s, switch_status,
local_rconn, remote_rconn);
}
+ if (s.command_acl[0]) {
+ executer_start(&secchan, &s);
+ }
for (;;) {
struct relay *r, *n;
OPT_NO_STP,
OPT_OUT_OF_BAND,
OPT_IN_BAND,
+ OPT_COMMAND_ACL,
+ OPT_COMMAND_DIR,
VLOG_OPTION_ENUMS
};
static struct option long_options[] = {
{"no-stp", no_argument, 0, OPT_NO_STP},
{"out-of-band", no_argument, 0, OPT_OUT_OF_BAND},
{"in-band", no_argument, 0, OPT_IN_BAND},
+ {"command-acl", required_argument, 0, OPT_COMMAND_ACL},
+ {"command-dir", required_argument, 0, OPT_COMMAND_DIR},
{"verbose", optional_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
s->burst_limit = 0;
s->enable_stp = false;
s->in_band = true;
+ s->command_acl = "";
+ s->command_dir = xasprintf("%s/commands", ofp_pkgdatadir);
for (;;) {
int c;
s->in_band = true;
break;
+ case OPT_COMMAND_ACL:
+ s->command_acl = (s->command_acl[0]
+ ? xasprintf("%s,%s", s->command_acl, optarg)
+ : optarg);
+ break;
+
+ case OPT_COMMAND_DIR:
+ s->command_dir = optarg;
+ break;
+
case 'l':
if (s->n_listeners >= MAX_MGMT) {
ofp_fatal(0,
" --no-stp disable 802.1D Spanning Tree Protocol\n"
"\nRate-limiting of \"packet-in\" messages to the controller:\n"
" --rate-limit[=PACKETS] max rate, in packets/s (default: 1000)\n"
- " --burst-limit=BURST limit on packet credit for idle time\n");
+ " --burst-limit=BURST limit on packet credit for idle time\n"
+ "\nRemote command execution options:\n"
+ " --command-acl=[!]GLOB[,[!]GLOB...] set allowed/denied commands\n"
+ " --command-dir=DIR set command dir (default: %s/commands)\n",
+ ofp_pkgdatadir);
daemon_usage();
vlog_usage();
printf("\nOther options:\n"
/* Spanning tree protocol. */
bool enable_stp;
+
+ /* Remote command execution. */
+ char *command_acl; /* Command white/blacklist, as shell globs. */
+ char *command_dir; /* Directory that contains commands. */
};
struct half {
\fBsecchan\fR and other processes, nor will it print replies sent by
the kernel in response to those messages.
+.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.)
+
.PP
The following commands can be used regardless of the connection
method. They apply to OpenFlow switches and controllers.
count * message_size / (duration / 1000.0));
}
+static void
+do_execute(const struct settings *s, int argc, char *argv[])
+{
+ struct vconn *vconn;
+ struct ofpbuf *request, *reply;
+ struct nicira_header *nicira;
+ struct nx_command_reply *ncr;
+ int status;
+ int i;
+
+ nicira = make_openflow(sizeof *nicira, OFPT_VENDOR, &request);
+ 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_transact(vconn, request, &reply), "transact");
+ if (reply->size < sizeof *ncr) {
+ ofp_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)) {
+ ofp_fatal(0, "reply is invalid");
+ }
+
+ status = ntohl(ncr->status);
+ 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 {
+ 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);
+}
+
static void do_help(const struct settings *s, int argc UNUSED,
char *argv[] UNUSED)
{
{ "probe", 1, 1, do_probe },
{ "ping", 1, 2, do_ping },
{ "benchmark", 3, 3, do_benchmark },
+ { "execute", 2, INT_MAX, do_execute },
{ NULL, 0, 0, NULL },
};