Add Nicira extension for remote command execution.
This commit is contained in:
1 parent
de5caa4906
commit
b2aec952c7
22 files changed
+740
-5
No files matched your search
@@ -4,6 +4,8 @@ man_MANS += secchan/secchan.8
|
||||
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 \
|
||||
@@ -13,6 +15,7 @@ secchan_secchan_SOURCES = \
|
||||
secchan/ratelimit.c \
|
||||
secchan/ratelimit.h \
|
||||
secchan/secchan.c \
|
||||
secchan/secchan.h \
|
||||
secchan/status.c \
|
||||
secchan/status.h \
|
||||
secchan/stp-secchan.c \
|
||||
@@ -26,3 +29,5 @@ secchan_secchan_LDADD = lib/libopenflow.a $(FAULT_LIBS) $(SSL_LIBS)
|
||||
|
||||
EXTRA_DIST += secchan/secchan.8.in
|
||||
DISTCLEANFILES += secchan/secchan.8
|
||||
|
||||
include secchan/commands/automake.mk
|
||||
@@ -0,0 +1,4 @@
|
||||
commandsdir = ${pkgdatadir}/commands
|
||||
dist_commands_SCRIPTS = \
|
||||
secchan/commands/reboot \
|
||||
secchan/commands/update
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#! /bin/sh
|
||||
reboot
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /bin/sh -e
|
||||
apt-get update -qy
|
||||
apt-get upgrade -qy
|
||||
@@ -0,0 +1,481 @@
|
||||
/* 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);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* 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 */
|
||||
@@ -360,6 +360,32 @@ at the switch. The default is \fB--no-stp\fR in this distribution,
|
||||
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
|
||||
|
||||
+26
-1
@@ -44,7 +44,9 @@
|
||||
#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"
|
||||
@@ -191,6 +193,9 @@ main(int argc, char *argv[])
|
||||
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;
|
||||
@@ -526,6 +531,8 @@ parse_options(int argc, char *argv[], struct settings *s)
|
||||
OPT_NO_STP,
|
||||
OPT_OUT_OF_BAND,
|
||||
OPT_IN_BAND,
|
||||
OPT_COMMAND_ACL,
|
||||
OPT_COMMAND_DIR,
|
||||
VLOG_OPTION_ENUMS
|
||||
};
|
||||
static struct option long_options[] = {
|
||||
@@ -543,6 +550,8 @@ parse_options(int argc, char *argv[], struct settings *s)
|
||||
{"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'},
|
||||
@@ -570,6 +579,8 @@ parse_options(int argc, char *argv[], struct settings *s)
|
||||
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;
|
||||
|
||||
@@ -660,6 +671,16 @@ parse_options(int argc, char *argv[], struct settings *s)
|
||||
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,
|
||||
@@ -779,7 +800,11 @@ usage(void)
|
||||
" --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"
|
||||
|
||||
@@ -81,6 +81,10 @@ struct settings {
|
||||
|
||||
/* 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 {
|
||||
|
||||
Reference in new issue
Block a user