From: Ben Pfaff Date: Thu, 9 Apr 2009 21:32:12 +0000 (-0700) Subject: Improve infrastructure for Unix socket-based local management. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=928ef32e0c8497833d37b509c8128637a6a63c16;p=openvswitch Improve infrastructure for Unix socket-based local management. "vlog_socket" was essentially a framework for management of a process over a simple Unix domain socket interface. Unfortunately it was a little too simple: * It was not extensible for use by clients other than vlog. * It was not reliable, since it was based on datagram sockets. * It tried to hide itself using poll_fd_callback(), instead of exposing itself through the poll loop as does almost every other entity in the build tree. This commit replaces vlog_socket by unixctl, which fixes these problems: * Arbitrary commands may now be registered. * Use of stream sockets makes it reliable. * The interface is exposed to clients. --- diff --git a/controller/controller.c b/controller/controller.c index 41f25478..fcebf675 100644 --- a/controller/controller.c +++ b/controller/controller.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford +/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford * Junior University * * We are making the OpenFlow specification and associated documentation @@ -50,10 +50,10 @@ #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-socket.h" #include "vlog.h" #define THIS_MODULE VLM_controller @@ -83,6 +83,7 @@ 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; @@ -135,9 +136,9 @@ main(int argc, char *argv[]) die_if_already_running(); daemonize(); - retval = vlog_server_listen(NULL, NULL); + retval = unixctl_server_create(NULL, &unixctl); if (retval) { - ofp_fatal(retval, "Could not listen for vlog connections"); + ofp_fatal(retval, "Could not listen for unixctl connections"); } while (n_switches > 0 || n_listeners > 0) { @@ -188,6 +189,8 @@ main(int argc, char *argv[]) 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++) { @@ -200,6 +203,7 @@ main(int argc, char *argv[]) rconn_recv_wait(sw->rconn); lswitch_wait(sw->lswitch); } + unixctl_server_wait(unixctl); poll_block(); } diff --git a/lib/automake.mk b/lib/automake.mk index 68502afc..66edf493 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -86,6 +86,8 @@ lib_libopenflow_a_SOURCES = \ lib/timeval.c \ lib/timeval.h \ lib/type-props.h \ + lib/unixctl.c \ + lib/unixctl.h \ lib/util.c \ lib/util.h \ lib/valgrind.h \ @@ -98,8 +100,6 @@ lib_libopenflow_a_SOURCES = \ lib/vconn.c \ lib/vconn.h \ lib/vlog-modules.def \ - lib/vlog-socket.c \ - lib/vlog-socket.h \ lib/vlog.c \ lib/vlog.h \ lib/xtoxll.h diff --git a/lib/socket-util.c b/lib/socket-util.c index d0ff0566..dc55f1b5 100644 --- a/lib/socket-util.c +++ b/lib/socket-util.c @@ -315,3 +315,46 @@ guess_netmask(uint32_t ip) : (ip >> 29) == 6 ? htonl(0xffffff00) /* Class C */ : htonl(0)); /* ??? */ } + +int +read_fully(int fd, void *p_, size_t size, size_t *bytes_read) +{ + uint8_t *p = p_; + + *bytes_read = 0; + while (size > 0) { + ssize_t retval = read(fd, p, size); + if (retval > 0) { + *bytes_read += retval; + size -= retval; + p += retval; + } else if (retval == 0) { + return EOF; + } else if (errno != EINTR) { + return errno; + } + } + return 0; +} + +int +write_fully(int fd, const void *p_, size_t size, size_t *bytes_written) +{ + const uint8_t *p = p_; + + *bytes_written = 0; + while (size > 0) { + ssize_t retval = write(fd, p, size); + if (retval > 0) { + *bytes_written += retval; + size -= retval; + p += retval; + } else if (retval == 0) { + VLOG_WARN("write returned 0"); + return EPROTO; + } else if (errno != EINTR) { + return errno; + } + } + return 0; +} diff --git a/lib/socket-util.h b/lib/socket-util.h index fd38a494..db6b0baa 100644 --- a/lib/socket-util.h +++ b/lib/socket-util.h @@ -50,4 +50,7 @@ int make_unix_socket(int style, bool nonblock, bool passcred, int get_unix_name_len(socklen_t sun_len); uint32_t guess_netmask(uint32_t ip); +int read_fully(int fd, void *, size_t, size_t *bytes_read); +int write_fully(int fd, const void *, size_t, size_t *bytes_written); + #endif /* socket-util.h */ diff --git a/lib/unixctl.c b/lib/unixctl.c new file mode 100644 index 00000000..81794f27 --- /dev/null +++ b/lib/unixctl.c @@ -0,0 +1,606 @@ +/* Copyright (c) 2008, 2009 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 +#include "unixctl.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dirs.h" +#include "dynamic-string.h" +#include "fatal-signal.h" +#include "list.h" +#include "ofpbuf.h" +#include "poll-loop.h" +#include "shash.h" +#include "socket-util.h" +#include "util.h" + +#ifndef SCM_CREDENTIALS +#include +#endif + +#define THIS_MODULE VLM_unixctl +#include "vlog.h" + +struct unixctl_command { + void (*cb)(struct unixctl_conn *, const char *args); +}; + +struct unixctl_conn { + struct list node; + int fd; + + enum { S_RECV, S_PROCESS, S_SEND } state; + struct ofpbuf in; + struct ds out; + size_t out_pos; +}; + +/* Server for control connection. */ +struct unixctl_server { + char *path; + int fd; + struct list conns; +}; + +/* Client for control connection. */ +struct unixctl_client { + char *connect_path; + char *bind_path; + FILE *stream; +}; + +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); + +static struct shash commands = SHASH_INITIALIZER(&commands); + +static void +unixctl_help(struct unixctl_conn *conn, const char *args UNUSED) +{ + struct ds ds = DS_EMPTY_INITIALIZER; + struct shash_node *node; + + ds_put_cstr(&ds, "The available commands are:\n"); + HMAP_FOR_EACH (node, struct shash_node, node, &commands.map) { + ds_put_format(&ds, "\t%s\n", node->name); + } + unixctl_command_reply(conn, 214, ds_cstr(&ds)); + ds_destroy(&ds); +} + +void +unixctl_command_register(const char *name, + void (*cb)(struct unixctl_conn *, const char *args)) +{ + struct unixctl_command *command; + + assert(!shash_find_data(&commands, name) + || shash_find_data(&commands, name) == cb); + command = xmalloc(sizeof *command); + command->cb = cb; + shash_add(&commands, name, command); +} + +static const char * +translate_reply_code(int code) +{ + switch (code) { + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 204: return "No Content"; + case 211: return "System Status"; + case 214: return "Help"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 500: return "Internal Server Error"; + case 501: return "Invalid Argument"; + case 503: return "Service Unavailable"; + default: return "Unknown"; + } +} + +void +unixctl_command_reply(struct unixctl_conn *conn, + int code, const char *body) +{ + struct ds *out = &conn->out; + + assert(conn->state == S_PROCESS); + conn->state = S_SEND; + conn->out_pos = 0; + + ds_clear(out); + ds_put_format(out, "%03d %s\n", code, translate_reply_code(code)); + if (body) { + const char *p; + for (p = body; *p != '\0'; ) { + size_t n = strcspn(p, "\n"); + + if (*p == '.') { + ds_put_char(out, '.'); + } + ds_put_buffer(out, p, n); + ds_put_char(out, '\n'); + p += n; + if (*p == '\n') { + p++; + } + } + } + ds_put_cstr(out, ".\n"); +} + +/* Creates a unixctl server listening on 'path', which may be: + * + * - NULL, in which case /..ctl is used. + * + * - A name that does not start with '/', in which case it is put in + * . + * + * - An absolute path (starting with '/') that gives the exact name of + * the Unix domain socket to listen on. + * + * A program that (optionally) daemonizes itself should call this function + * *after* daemonization, so that the socket name contains the pid of the + * daemon instead of the pid of the program that exited. (Otherwise, "vlogconf + * --target .pid" will fail.) + * + * Returns 0 if successful, otherwise a positive errno value. If successful, + * sets '*serverp' to the new unixctl_server, otherwise to NULL. */ +int +unixctl_server_create(const char *path, struct unixctl_server **serverp) +{ + struct unixctl_server *server; + int error; + + unixctl_command_register("help", unixctl_help); + + server = xmalloc(sizeof *server); + list_init(&server->conns); + + if (path) { + if (path[0] == '/') { + server->path = xstrdup(path); + } else { + server->path = xasprintf("%s/%s", ofp_rundir, path); + } + } else { + server->path = xasprintf("%s/%s.%ld.ctl", ofp_rundir, + program_name, (long int) getpid()); + } + + server->fd = make_unix_socket(SOCK_STREAM, true, false, server->path, + NULL); + if (server->fd < 0) { + error = -server->fd; + fprintf(stderr, "Could not initialize control socket %s (%s)\n", + server->path, strerror(error)); + goto error; + } + + if (chmod(server->path, S_IRUSR | S_IWUSR) < 0) { + error = errno; + fprintf(stderr, "Failed to chmod control socket %s (%s)\n", + server->path, strerror(error)); + goto error; + } + + if (listen(server->fd, 10) < 0) { + error = errno; + fprintf(stderr, "Failed to listen on control socket %s (%s)\n", + server->path, strerror(error)); + goto error; + } + + *serverp = server; + return 0; + +error: + if (server->fd >= 0) { + close(server->fd); + } + free(server->path); + free(server); + *serverp = NULL; + return error; +} + +static void +new_connection(struct unixctl_server *server, int fd) +{ + struct unixctl_conn *conn; + + set_nonblocking(fd); + + conn = xmalloc(sizeof *conn); + list_push_back(&server->conns, &conn->node); + conn->fd = fd; + conn->state = S_RECV; + ofpbuf_init(&conn->in, 128); + ds_init(&conn->out); + conn->out_pos = 0; +} + +static int +run_connection_output(struct unixctl_conn *conn) +{ + while (conn->out_pos < conn->out.length) { + size_t bytes_written; + int error; + + error = write_fully(conn->fd, conn->out.string + conn->out_pos, + conn->out.length - conn->out_pos, &bytes_written); + conn->out_pos += bytes_written; + if (error) { + return error; + } + } + conn->state = S_RECV; + return 0; +} + +static void +process_command(struct unixctl_conn *conn, char *s) +{ + struct unixctl_command *command; + size_t name_len; + char *name, *args; + + conn->state = S_PROCESS; + + name = s; + name_len = strcspn(name, " "); + args = name + name_len; + args += strspn(args, " "); + name[name_len] = '\0'; + + command = shash_find_data(&commands, name); + if (command) { + command->cb(conn, args); + } else { + char *msg = xasprintf("\"%s\" is not a valid command", name); + unixctl_command_reply(conn, 400, msg); + free(msg); + } +} + +static int +run_connection_input(struct unixctl_conn *conn) +{ + for (;;) { + size_t bytes_read; + char *newline; + int error; + + newline = memchr(conn->in.data, '\n', conn->in.size); + if (newline) { + char *command = conn->in.data; + size_t n = newline - command + 1; + + if (n > 0 && newline[-1] == '\r') { + newline--; + } + *newline = '\0'; + + process_command(conn, command); + + ofpbuf_pull(&conn->in, n); + if (!conn->in.size) { + ofpbuf_clear(&conn->in); + } + return 0; + } + + ofpbuf_prealloc_tailroom(&conn->in, 128); + error = read_fully(conn->fd, ofpbuf_tail(&conn->in), + ofpbuf_tailroom(&conn->in), &bytes_read); + conn->in.size += bytes_read; + if (conn->in.size > 65536) { + VLOG_WARN_RL(&rl, "excess command length, killing connection"); + return EPROTO; + } + if (error) { + if (error == EAGAIN || error == EWOULDBLOCK) { + if (!bytes_read) { + return EAGAIN; + } + } else { + if (error != EOF || conn->in.size != 0) { + VLOG_WARN_RL(&rl, "read failed: %s", + (error == EOF + ? "connection dropped mid-command" + : strerror(error))); + } + return error; + } + } + } +} + +static int +run_connection(struct unixctl_conn *conn) +{ + int old_state; + do { + int error; + + old_state = conn->state; + switch (conn->state) { + case S_RECV: + error = run_connection_input(conn); + break; + + case S_PROCESS: + error = 0; + break; + + case S_SEND: + error = run_connection_output(conn); + break; + + default: + NOT_REACHED(); + } + if (error) { + return error; + } + } while (conn->state != old_state); + return 0; +} + +static void +kill_connection(struct unixctl_conn *conn) +{ + list_remove(&conn->node); + ofpbuf_uninit(&conn->in); + ds_destroy(&conn->out); + close(conn->fd); + free(conn); +} + +void +unixctl_server_run(struct unixctl_server *server) +{ + struct unixctl_conn *conn, *next; + int i; + + for (i = 0; i < 10; i++) { + int fd = accept(server->fd, NULL, NULL); + if (fd < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + VLOG_WARN_RL(&rl, "accept failed: %s", strerror(errno)); + } + break; + } + new_connection(server, fd); + } + + LIST_FOR_EACH_SAFE (conn, next, + struct unixctl_conn, node, &server->conns) { + int error = run_connection(conn); + if (error && error != EAGAIN) { + kill_connection(conn); + } + } +} + +void +unixctl_server_wait(struct unixctl_server *server) +{ + struct unixctl_conn *conn; + + poll_fd_wait(server->fd, POLLIN); + LIST_FOR_EACH (conn, struct unixctl_conn, node, &server->conns) { + if (conn->state == S_RECV) { + poll_fd_wait(conn->fd, POLLIN); + } else if (conn->state == S_SEND) { + poll_fd_wait(conn->fd, POLLOUT); + } + } +} + +/* Destroys 'server' and stops listening for connections. */ +void +unixctl_server_destroy(struct unixctl_server *server) +{ + if (server) { + struct unixctl_conn *conn, *next; + + LIST_FOR_EACH_SAFE (conn, next, + struct unixctl_conn, node, &server->conns) { + kill_connection(conn); + } + + close(server->fd); + unlink(server->path); + fatal_signal_remove_file_to_unlink(server->path); + free(server->path); + free(server); + } +} + +/* Connects to a Vlog server socket. 'path' should be the name of a Vlog + * server socket. If it does not start with '/', it will be prefixed with + * ofp_rundir (e.g. /var/run). + * + * Returns 0 if successful, otherwise a positive errno value. If successful, + * sets '*clientp' to the new unixctl_client, otherwise to NULL. */ +int +unixctl_client_create(const char *path, struct unixctl_client **clientp) +{ + static int counter; + struct unixctl_client *client; + int error; + int fd = -1; + + /* Determine location. */ + client = xmalloc(sizeof *client); + if (path[0] == '/') { + client->connect_path = xstrdup(path); + } else { + client->connect_path = xasprintf("%s/%s", ofp_rundir, path); + } + client->bind_path = xasprintf("/tmp/vlog.%ld.%d", + (long int) getpid(), counter++); + + /* Open socket. */ + fd = make_unix_socket(SOCK_STREAM, false, false, + client->bind_path, client->connect_path); + if (fd < 0) { + error = -fd; + goto error; + } + + /* Bind socket to stream. */ + client->stream = fdopen(fd, "r+"); + if (!client->stream) { + error = errno; + VLOG_WARN("%s: fdopen failed (%s)", + client->connect_path, strerror(error)); + goto error; + } + *clientp = client; + return 0; + +error: + if (fd >= 0) { + close(fd); + } + free(client->connect_path); + free(client->bind_path); + free(client); + *clientp = NULL; + return error; +} + +/* Destroys 'client'. */ +void +unixctl_client_destroy(struct unixctl_client *client) +{ + if (client) { + unlink(client->bind_path); + fatal_signal_remove_file_to_unlink(client->bind_path); + free(client->bind_path); + free(client->connect_path); + fclose(client->stream); + free(client); + } +} + +/* Sends 'request' to the server socket and waits for a reply. Returns 0 if + * successful, otherwise to a positive errno value. If successful, sets + * '*reply' to the reply, which the caller must free, otherwise to NULL. */ +int +unixctl_client_transact(struct unixctl_client *client, + const char *request, + int *reply_code, char **reply_body) +{ + struct ds line = DS_EMPTY_INITIALIZER; + struct ds reply = DS_EMPTY_INITIALIZER; + int error; + + /* Send 'request' to server. Add a new-line if 'request' didn't end in + * one. */ + fputs(request, client->stream); + if (request[0] == '\0' || request[strlen(request) - 1] != '\n') { + putc('\n', client->stream); + } + if (ferror(client->stream)) { + VLOG_WARN("error sending request to %s: %s", + client->connect_path, strerror(errno)); + return errno; + } + + /* Wait for response. */ + *reply_code = -1; + for (;;) { + const char *s; + + error = ds_get_line(&line, client->stream); + if (error) { + VLOG_WARN("error reading reply from %s: %s", + client->connect_path, + (error == EOF ? "unexpected end of file" + : strerror(error))); + goto error; + } + + s = ds_cstr(&line); + if (*reply_code == -1) { + if (!isdigit(s[0]) || !isdigit(s[1]) || !isdigit(s[2])) { + VLOG_WARN("reply from %s does not start with 3-digit code", + client->connect_path); + error = EPROTO; + goto error; + } + sscanf(s, "%3d", reply_code); + } else { + if (s[0] == '.') { + if (s[1] == '\0') { + break; + } + s++; + } + ds_put_cstr(&reply, s); + ds_put_char(&reply, '\n'); + } + } + *reply_body = ds_cstr(&reply); + ds_destroy(&line); + return 0; + +error: + ds_destroy(&line); + ds_destroy(&reply); + *reply_code = 0; + *reply_body = NULL; + return error == EOF ? EPROTO : error; +} + +/* Returns the path of the server socket to which 'client' is connected. The + * caller must not modify or free the returned string. */ +const char * +unixctl_client_target(const struct unixctl_client *client) +{ + return client->connect_path; +} diff --git a/lib/unixctl.h b/lib/unixctl.h new file mode 100644 index 00000000..58120599 --- /dev/null +++ b/lib/unixctl.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2008, 2009 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 UNIXCTL_H +#define UNIXCTL_H 1 + +/* Server for Unix domain socket control connection. */ +struct unixctl_server; +int unixctl_server_create(const char *path, struct unixctl_server **); +void unixctl_server_run(struct unixctl_server *); +void unixctl_server_wait(struct unixctl_server *); +void unixctl_server_destroy(struct unixctl_server *); + +/* Client for Unix domain socket control connection. */ +struct unixctl_client; +int unixctl_client_create(const char *path, struct unixctl_client **); +void unixctl_client_destroy(struct unixctl_client *); +int unixctl_client_transact(struct unixctl_client *, + const char *request, + int *reply_code, char **reply_body); +const char *unixctl_client_target(const struct unixctl_client *); + +/* Command registration. */ +struct unixctl_conn; +void unixctl_command_register(const char *name, + void (*cb)(struct unixctl_conn *, + const char *args)); +void unixctl_command_reply(struct unixctl_conn *, int code, + const char *body); + +#endif /* unixctl.h */ diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index f79fe2b5..e6b73811 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -45,13 +45,13 @@ VLOG_MODULE(switch) VLOG_MODULE(terminal) VLOG_MODULE(timeval) VLOG_MODULE(socket_util) +VLOG_MODULE(unixctl) VLOG_MODULE(vconn_tcp) VLOG_MODULE(vconn_ssl) VLOG_MODULE(vconn_stream) VLOG_MODULE(vconn_unix) VLOG_MODULE(vconn) VLOG_MODULE(vlog) -VLOG_MODULE(vlog_socket) VLOG_MODULE(wcelim) VLOG_MODULE(vswitchd) VLOG_MODULE(xenserver) diff --git a/lib/vlog-socket.c b/lib/vlog-socket.c deleted file mode 100644 index 11bf2252..00000000 --- a/lib/vlog-socket.c +++ /dev/null @@ -1,482 +0,0 @@ -/* Copyright (c) 2008, 2009 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 -#include "vlog-socket.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "daemon.h" -#include "fatal-signal.h" -#include "poll-loop.h" -#include "socket-util.h" -#include "timeval.h" -#include "util.h" - -#ifndef SCM_CREDENTIALS -#include -#endif - -#define THIS_MODULE VLM_vlog_socket -#include "vlog.h" - -/* Server for Vlog control connection. */ -struct vlog_server { - struct poll_waiter *waiter; - char *path; - int fd; -}; - -static void poll_server(int fd, short int events, void *server_); - -/* Start listening for connections from clients and processing their - * requests. 'path' may be: - * - * - NULL, in which case the default socket path is used. (Only one - * Vlog_server_socket per process can use the default path.) - * - * - A name that does not start with '/', in which case it is appended to - * the default socket path. - * - * - An absolute path (starting with '/') that gives the exact name of - * the Unix domain socket to listen on. - * - * A program that (optionally) daemonizes itself should call this function - * *after* daemonization, so that the socket name contains the pid of the - * daemon instead of the pid of the program that exited. (Otherwise, "vlogconf - * --target .pid" will fail.) - * - * Returns 0 if successful, otherwise a positive errno value. If successful, - * sets '*serverp' to the new vlog_server, otherwise to NULL. */ -int -vlog_server_listen(const char *path, struct vlog_server **serverp) -{ - struct vlog_server *server = xmalloc(sizeof *server); - - if (path && path[0] == '/') { - server->path = xstrdup(path); - } else { - server->path = xasprintf("/tmp/vlogs.%ld%s", - (long int) getpid(), path ? path : ""); - } - - server->fd = make_unix_socket(SOCK_DGRAM, true, true, server->path, NULL); - if (server->fd < 0) { - int fd = server->fd; - fprintf(stderr, "Could not initialize vlog configuration socket: %s\n", - strerror(-server->fd)); - free(server->path); - free(server); - if (serverp) { - *serverp = NULL; - } - return fd; - } - - server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server); - - if (serverp) { - *serverp = server; - } - return 0; -} - -/* Destroys 'server' and stops listening for connections. */ -void -vlog_server_close(struct vlog_server *server) -{ - if (server) { - poll_cancel(server->waiter); - close(server->fd); - unlink(server->path); - fatal_signal_remove_file_to_unlink(server->path); - free(server->path); - free(server); - } -} - -static int -recv_with_creds(const struct vlog_server *server, - char *cmd_buf, size_t cmd_buf_size, - struct sockaddr_un *un, socklen_t *un_len) -{ -#ifdef SCM_CREDENTIALS - /* Read a message and control messages from 'fd'. */ - char cred_buf[CMSG_SPACE(sizeof(struct ucred))]; - ssize_t n; - struct iovec iov; - struct msghdr msg; - struct ucred* cred; - struct cmsghdr* cmsg; - - iov.iov_base = cmd_buf; - iov.iov_len = cmd_buf_size - 1; - - memset(&msg, 0, sizeof msg); - msg.msg_name = un; - msg.msg_namelen = sizeof *un; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = cred_buf; - msg.msg_controllen = sizeof cred_buf; - - n = recvmsg(server->fd, &msg, 0); - *un_len = msg.msg_namelen; - if (n < 0) { - return errno; - } - cmd_buf[n] = '\0'; - - /* Ensure that the message has credentials ensuring that it was sent - * from the same user who started us, or by root. */ - cred = NULL; - for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if (cmsg->cmsg_level == SOL_SOCKET - && cmsg->cmsg_type == SCM_CREDENTIALS) { - cred = (struct ucred *) CMSG_DATA(cmsg); - } else if (cmsg->cmsg_level == SOL_SOCKET - && cmsg->cmsg_type == SCM_RIGHTS) { - /* Anyone can send us fds. If we don't close them, then that's - * a DoS: the sender can overflow our fd table. */ - int* fds = (int *) CMSG_DATA(cmsg); - size_t n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof *fds; - size_t i; - for (i = 0; i < n_fds; i++) { - close(fds[i]); - } - } - } - if (!cred) { - fprintf(stderr, "vlog: config message lacks credentials\n"); - return -1; - } else if (cred->uid && cred->uid != getuid()) { - fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n", - (long int) cred->uid, (long int) getuid()); - return -1; - } - - return 0; -#else /* !SCM_CREDENTIALS */ - socklen_t len; - ssize_t n; - struct stat s; - time_t recent; - - /* Receive a message. */ - len = sizeof *un; - n = recvfrom(server->fd, cmd_buf, cmd_buf_size - 1, 0, - (struct sockaddr *) un, &len); - *un_len = len; - if (n < 0) { - return errno; - } - cmd_buf[n] = '\0'; - - len -= offsetof(struct sockaddr_un, sun_path); - un->sun_path[len] = '\0'; - if (stat(un->sun_path, &s) < 0) { - fprintf(stderr, "vlog: config message from inaccessible socket: %s\n", - strerror(errno)); - return -1; - } - if (!S_ISSOCK(s.st_mode)) { - fprintf(stderr, "vlog: config message not from a socket\n"); - return -1; - } - recent = time_now() - 30; - if (s.st_atime < recent || s.st_ctime < recent || s.st_mtime < recent) { - fprintf(stderr, "vlog: config socket too old\n"); - return -1; - } - if (s.st_uid && s.st_uid != getuid()) { - fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n", - (long int) s.st_uid, (long int) getuid()); - return -1; - } - return 0; -#endif /* !SCM_CREDENTIALS */ -} - -/* Processes incoming requests for 'server'. */ -static void -poll_server(int fd UNUSED, short int events UNUSED, void *server_) -{ - struct vlog_server *server = server_; - for (;;) { - char cmd_buf[512]; - struct sockaddr_un un; - socklen_t un_len; - char *reply; - int error; - - error = recv_with_creds(server, cmd_buf, sizeof cmd_buf, &un, &un_len); - if (error > 0) { - if (error != EAGAIN && error != EWOULDBLOCK) { - fprintf(stderr, "vlog: reading configuration socket: %s", - strerror(errno)); - } - break; - } else if (error < 0) { - continue; - } - - /* Process message and send reply. */ - if (!strncmp(cmd_buf, "set ", 4)) { - char *msg = vlog_set_levels_from_string(cmd_buf + 4); - reply = msg ? msg : xstrdup("ack"); - } else if (!strcmp(cmd_buf, "list")) { - reply = vlog_get_levels(); - } else if (!strcmp(cmd_buf, "reopen")) { - int error = vlog_reopen_log_file(); - reply = (error - ? xasprintf("could not reopen log file \"%s\": %s", - vlog_get_log_file(), strerror(error)) - : xstrdup("ack")); - } else { - reply = xstrdup("nak"); - } - sendto(server->fd, reply, strlen(reply), 0, - (struct sockaddr*) &un, un_len); - free(reply); - } - server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server); -} - -/* Client for Vlog control connection. */ - -struct vlog_client { - char *connect_path; - char *bind_path; - int fd; -}; - -/* Connects to a Vlog server socket. 'path' may be: - * - * - A string that starts with a PID. If a non-null, non-absolute name - * was passed to Vlog_server_socket::listen(), then it must follow the - * PID in 'path'. - * - * - An absolute path (starting with '/') to a Vlog server socket or a - * pidfile. If it is a pidfile, the pidfile will be read and translated - * into a Vlog server socket file name. - * - * - A relative path, which is translated into a pidfile name and then - * treated as above. - * - * Returns 0 if successful, otherwise a positive errno value. If successful, - * sets '*clientp' to the new vlog_client, otherwise to NULL. */ -int -vlog_client_connect(const char *path, struct vlog_client **clientp) -{ - static int counter; - struct vlog_client *client; - struct stat s; - int error; - - client = xmalloc(sizeof *client); - if (path[0] == '/') { - client->connect_path = xstrdup(path); - } else if (isdigit((unsigned char) path[0])) { - client->connect_path = xasprintf("/tmp/vlogs.%s", path); - } else { - client->connect_path = make_pidfile_name(path); - } - client->bind_path = NULL; - - if (stat(client->connect_path, &s)) { - error = errno; - VLOG_WARN("could not stat \"%s\": %s", - client->connect_path, strerror(error)); - goto error; - } else if (S_ISREG(s.st_mode)) { - pid_t pid = read_pidfile(client->connect_path); - if (pid < 0) { - error = -pid; - VLOG_WARN("could not read pidfile \"%s\": %s", - client->connect_path, strerror(error)); - goto error; - } - free(client->connect_path); - client->connect_path = xasprintf("/tmp/vlogs.%ld", (long int) pid); - } - client->bind_path = xasprintf("/tmp/vlog.%ld.%d", - (long int) getpid(), counter++); - client->fd = make_unix_socket(SOCK_DGRAM, false, false, - client->bind_path, client->connect_path); - if (client->fd < 0) { - error = -client->fd; - goto error; - } - *clientp = client; - return 0; - -error: - free(client->connect_path); - free(client->bind_path); - free(client); - *clientp = NULL; - return error; -} - -/* Destroys 'client'. */ -void -vlog_client_close(struct vlog_client *client) -{ - if (client) { - unlink(client->bind_path); - fatal_signal_remove_file_to_unlink(client->bind_path); - free(client->bind_path); - free(client->connect_path); - close(client->fd); - free(client); - } -} - -/* Sends 'request' to the server socket that 'client' is connected to. Returns - * 0 if successful, otherwise a positive errno value. */ -int -vlog_client_send(struct vlog_client *client, const char *request) -{ -#ifdef SCM_CREDENTIALS - struct ucred cred; - struct iovec iov; - char buf[CMSG_SPACE(sizeof cred)]; - struct msghdr msg; - struct cmsghdr* cmsg; - ssize_t nbytes; - - cred.pid = getpid(); - cred.uid = getuid(); - cred.gid = getgid(); - - iov.iov_base = (void*) request; - iov.iov_len = strlen(request); - - memset(&msg, 0, sizeof msg); - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = buf; - msg.msg_controllen = sizeof buf; - - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(sizeof cred); - memcpy(CMSG_DATA(cmsg), &cred, sizeof cred); - msg.msg_controllen = cmsg->cmsg_len; - - nbytes = sendmsg(client->fd, &msg, 0); -#else /* !SCM_CREDENTIALS */ - ssize_t nbytes = send(client->fd, request, strlen(request), 0); -#endif /* !SCM_CREDENTIALS */ - if (nbytes > 0) { - return nbytes == strlen(request) ? 0 : ENOBUFS; - } else { - return errno; - } -} - -/* Attempts to receive a response from the server socket that 'client' is - * connected to. Returns 0 if successful, otherwise a positive errno value. - * If successful, sets '*reply' to the reply, which the caller must free, - * otherwise to NULL. */ -int -vlog_client_recv(struct vlog_client *client, char **reply) -{ - struct pollfd pfd; - int nfds; - char buffer[65536]; - ssize_t nbytes; - - *reply = NULL; - - pfd.fd = client->fd; - pfd.events = POLLIN; - nfds = time_poll(&pfd, 1, 1000); - if (nfds == 0) { - return ETIMEDOUT; - } else if (nfds < 0) { - return -nfds; - } - - nbytes = read(client->fd, buffer, sizeof buffer - 1); - if (nbytes < 0) { - return errno; - } else { - buffer[nbytes] = '\0'; - *reply = xstrdup(buffer); - return 0; - } -} - -/* Sends 'request' to the server socket and waits for a reply. Returns 0 if - * successful, otherwise to a positive errno value. If successful, sets - * '*reply' to the reply, which the caller must free, otherwise to NULL. */ -int -vlog_client_transact(struct vlog_client *client, - const char *request, char **reply) -{ - int i; - - /* Retry up to 3 times. */ - for (i = 0; i < 3; ++i) { - int error = vlog_client_send(client, request); - if (error) { - *reply = NULL; - return error; - } - error = vlog_client_recv(client, reply); - if (error != ETIMEDOUT) { - return error; - } - } - *reply = NULL; - return ETIMEDOUT; -} - -/* Returns the path of the server socket to which 'client' is connected. The - * caller must not modify or free the returned string. */ -const char * -vlog_client_target(const struct vlog_client *client) -{ - return client->connect_path; -} diff --git a/lib/vlog-socket.h b/lib/vlog-socket.h deleted file mode 100644 index ac25f63c..00000000 --- a/lib/vlog-socket.h +++ /dev/null @@ -1,52 +0,0 @@ -/* 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 VLOG_SOCKET_H -#define VLOG_SOCKET_H 1 - -/* Server for Vlog control connection. */ -struct vlog_server; -int vlog_server_listen(const char *path, struct vlog_server **); -void vlog_server_close(struct vlog_server *); - -/* Client for Vlog control connection. */ -struct vlog_client; -int vlog_client_connect(const char *path, struct vlog_client **); -void vlog_client_close(struct vlog_client *); -int vlog_client_send(struct vlog_client *, const char *request); -int vlog_client_recv(struct vlog_client *, char **reply); -int vlog_client_transact(struct vlog_client *, - const char *request, char **reply); -const char *vlog_client_target(const struct vlog_client *); - -#endif /* vlog-socket.h */ diff --git a/lib/vlog.c b/lib/vlog.c index 63d274cc..c6452ad6 100644 --- a/lib/vlog.c +++ b/lib/vlog.c @@ -47,6 +47,7 @@ #include "dynamic-string.h" #include "sat-math.h" #include "timeval.h" +#include "unixctl.h" #include "util.h" #define THIS_MODULE VLM_vlog @@ -400,6 +401,37 @@ vlog_set_verbosity(const char *arg) } } +static void +vlog_unixctl_set(struct unixctl_conn *conn, const char *args) +{ + char *msg = vlog_set_levels_from_string(args); + unixctl_command_reply(conn, msg ? 501 : 202, msg); + free(msg); +} + +static void +vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED) +{ + char *msg = vlog_get_levels(); + unixctl_command_reply(conn, 200, msg); + free(msg); +} + +static void +vlog_unixctl_reopen(struct unixctl_conn *conn, const char *args UNUSED) +{ + if (log_file_name) { + int error = vlog_reopen_log_file(); + if (error) { + unixctl_command_reply(conn, 503, strerror(errno)); + } else { + unixctl_command_reply(conn, 202, NULL); + } + } else { + unixctl_command_reply(conn, 403, "Logging to file not configured"); + } +} + /* Initializes the logging subsystem. */ void vlog_init(void) @@ -419,6 +451,10 @@ vlog_init(void) strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm); VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now); } + + unixctl_command_register("vlog/set", vlog_unixctl_set); + unixctl_command_register("vlog/list", vlog_unixctl_list); + unixctl_command_register("vlog/reopen", vlog_unixctl_reopen); } /* Closes the logging subsystem. */ diff --git a/secchan/main.c b/secchan/main.c index d9d44b9d..334e3196 100644 --- a/secchan/main.c +++ b/secchan/main.c @@ -62,10 +62,10 @@ #include "status.h" #include "svec.h" #include "timeval.h" +#include "unixctl.h" #include "util.h" #include "vconn-ssl.h" #include "vconn.h" -#include "vlog-socket.h" #include "vlog.h" #define THIS_MODULE VLM_secchan @@ -131,6 +131,7 @@ static void usage(void) NO_RETURN; int main(int argc, char *argv[]) { + struct unixctl_server *unixctl; struct ofproto *ofproto; struct ofsettings s; int error; @@ -146,9 +147,9 @@ main(int argc, char *argv[]) daemonize(); /* Start listening for vlogconf requests. */ - error = vlog_server_listen(NULL, NULL); + error = unixctl_server_create(NULL, &unixctl); if (error) { - ofp_fatal(error, "Could not listen for vlog connections"); + ofp_fatal(error, "Could not listen for unixctl connections"); } VLOG_INFO("OpenFlow reference implementation version %s", VERSION BUILDNR); @@ -213,7 +214,10 @@ main(int argc, char *argv[]) if (error) { ofp_fatal(error, "unrecoverable datapath error"); } + unixctl_server_run(unixctl); + ofproto_wait(ofproto); + unixctl_server_wait(unixctl); poll_block(); } diff --git a/utilities/ofp-discover.c b/utilities/ofp-discover.c index 55f40429..48627a34 100644 --- a/utilities/ofp-discover.c +++ b/utilities/ofp-discover.c @@ -48,8 +48,8 @@ #include "netdev.h" #include "poll-loop.h" #include "timeval.h" +#include "unixctl.h" #include "util.h" -#include "vlog-socket.h" #include "vlog.h" #define THIS_MODULE VLM_ofp_discover @@ -88,6 +88,7 @@ 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; @@ -129,9 +130,9 @@ main(int argc, char *argv[]) ofp_fatal(0, "%s: %s", accept_controller_re, buffer); } - retval = vlog_server_listen(NULL, NULL); + retval = unixctl_server_create(NULL, &unixctl); if (retval) { - ofp_fatal(retval, "Could not listen for vlog connections"); + ofp_fatal(retval, "Could not listen for unixctl connections"); } die_if_already_running(); @@ -205,10 +206,12 @@ main(int argc, char *argv[]) } } } + 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(); } diff --git a/utilities/vlogconf.8.in b/utilities/vlogconf.8.in index 0baf6d6f..7cbd374e 100644 --- a/utilities/vlogconf.8.in +++ b/utilities/vlogconf.8.in @@ -1,4 +1,10 @@ -.TH vlogconf 8 "June 2008" "OpenFlow" "OpenFlow Manual" +.\" -*- nroff -*- +.de IQ +. br +. ns +. IP "\\$1" +.. +.TH vlogconf 8 "April 2009" "OpenFlow" "OpenFlow Manual" .ds PN vlogconf .SH NAME @@ -9,7 +15,7 @@ vlogconf \- configuration utility for OpenFlow logging in userspace .sp 1 The available \fItarget\fR options are: .br -[\fB-a\fR | \fB--all\fR] [\fB-t\fR \fIpid\fR | \fB--target=\fIpid\fR] +[\fB-t\fR \fIpid\fR | \fB--target=\fIpid\fR] .sp 1 The available \fIaction\fR options are: .br @@ -17,6 +23,7 @@ The available \fIaction\fR options are: \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 \fBvlogconf\fR program configures the logging system used by @@ -24,48 +31,24 @@ OpenFlow userspace programs. The logging configuration may be modified while OpenFlow programs are running. \fBvlogconf\fR applies one or more actions to each of one or more -target processes. Targets may be specified as: +target processes. Targets may be specified using: -.TP -\fB-a\fR, \fB--all\fR -All running processes that \fBvlogconf\fR can control. - -.TP -\fB-t \fItarget\fR, \fB--target=\fItarget\fR -The specified \fItarget\fR, which must take one of the following forms: - -.RS -.IP \(bu -A PID (process ID). - -.IP \(bu -An absolute path (beginning with `/') to the Unix domain socket for a -\fBvlogconf\fR-controllable process. - -.IP \(bu -An absolute path (beginning with `/') to a pidfile (created by, e.g., -passing the \fB-P\fR or \fB--pidfile\fR option to one of the OpenFlow -programs). - -.IP \(bu -None of the above, in which case \fItarget\fR prefixed by -\fB@RUNDIR@/\fR must match one of the cases for absolute paths listed -above. (The default name for a program's pidfile is -\fB@RUNDIR@/\fIprogram\fB.pid\fR, so this means that, say, -\fBsecchan\fR's default pidfile may be referred to simply as -\fBsecchan.pid\fR.) -.RE +.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 \fBvlogconf\fR-controllable process. If \fIsocket\fR does not +begin with \fB/\fR, it is treated as relative to \fB@RUNDIR@\fR. .PP The available actions are: -.TP -\fB-l\fR, \fB--list\fR +.IP "\fB-l\fR" +.IQ "\fB--list\fR" Print the list of known modules and their current logging levels to stdout. -.TP -\fB-s\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] +.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 @@ -79,8 +62,8 @@ logging levels for both facilities. If it is omitted, minimum severity of a message for it to be logged. If it is omitted, \fIlevel\fR defaults to \fBdbg\fR. -.TP -\fB-s PATTERN:\fIfacility\fB:\fIpattern\fR, \fB--set=PATTERN:\fIfacility\fB:\fIpattern\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 @@ -89,53 +72,42 @@ literally to the log, but special escapes beginning with \fB%\fR are expanded as follows: .RS -.TP -\fB%A\fR +.IP \fB%A\fR The name of the application logging the message, e.g. \fBsecchan\fR. -.TP -\fB%c\fR +.IP \fB%c\fR The name of the module (as shown by \fBvlogconf --list\fR) logging the message. -.TP -\fB%d\fR +.IP \fB%d\fR The current date and time in ISO 8601 format (YYYY-MM-DD HH:MM:SS). -.TP -\fB%d{\fIformat\fB}\fR +.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). -.TP -\fB%m\fR +.IP \fB%m\fR The message being logged. -.TP -\fB%N\fR +.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. -.TP -\fB%n\fR +.IP \fB%n\fR A new-line. -.TP -\fB%p\fR +.IP \fB%p\fR The level at which the message is logged, e.g. \fBDBG\fR. -.TP -\fB%P\fR +.IP \fB%P\fR The program's process ID (pid), as a decimal number. -.TP -\fB%r\fR +.IP \fB%r\fR The number of milliseconds elapsed from the start of the application to the time the message was logged. -.TP -\fB%%\fR +.IP \fB%%\fR A literal \fB%\fR. .RE @@ -144,18 +116,15 @@ A few options may appear between the \fB%\fR and the format specifier character, in this order: .RS -.TP -\fB-\fR +.IP \fB-\fR Left justify the escape's expansion within its field width. Right justification is the default. -.TP -\fB0\fR +.IP \fB0\fR Pad the field to the field width with \fB0\fRs. Padding with spaces is the default. -.TP -\fIwidth\fR +.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.) @@ -165,12 +134,18 @@ width. (A field wider than \fIwidth\fR is not truncated to fit.) 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. -.TP -\fB-r\fR, \fB--reopen\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.) +.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 diff --git a/utilities/vlogconf.c b/utilities/vlogconf.c index 5c48f724..1ce01bda 100644 --- a/utilities/vlogconf.c +++ b/utilities/vlogconf.c @@ -44,18 +44,15 @@ #include "command-line.h" #include "compiler.h" #include "timeval.h" +#include "unixctl.h" #include "util.h" -#include "vlog-socket.h" static void usage(char *prog_name, int exit_code) { printf("Usage: %s [TARGET] [ACTION...]\n" "Targets:\n" - " -a, --all Apply to all targets (default)\n" - " -t, --target=TARGET Specify target program, as a pid, a\n" - " pidfile, or an absolute path to a Unix\n" - " domain socket\n" + " -t, --target=TARGET Path to Unix domain socket\n" "Actions:\n" " -l, --list List current settings\n" " -s, --set=MODULE[:FACILITY[:LEVEL]]\n" @@ -64,47 +61,66 @@ usage(char *prog_name, int exit_code) " 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" " -h, --help Print this helpful information\n", prog_name); exit(exit_code); } static char * -transact(struct vlog_client *client, const char *request, bool *ok) +transact(struct unixctl_client *client, const char *request, bool *ok) { + int code; char *reply; - int error = vlog_client_transact(client, request, &reply); + int error = unixctl_client_transact(client, request, &code, &reply); if (error) { fprintf(stderr, "%s: transaction error: %s\n", - vlog_client_target(client), strerror(error)); + 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; } - return reply ? reply : xstrdup(""); } static void -transact_ack(struct vlog_client *client, const char* request, bool *ok) +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 = vlog_client_transact(client, request, &reply); + int error = unixctl_client_transact(client, request, &code, &reply); if (error) { fprintf(stderr, "%s: transaction error: %s\n", - vlog_client_target(client), strerror(error)); - *ok = false; - } else if (strcmp(reply, "ack")) { - fprintf(stderr, "Received unexpected reply from %s: %s\n", - vlog_client_target(client), reply); + 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); + } } - free(reply); } static void -add_target(struct vlog_client ***clients, size_t *n_clients, +add_target(struct unixctl_client ***clients, size_t *n_clients, const char *path, bool *ok) { - struct vlog_client *client; - int error = vlog_client_connect(path, &client); + struct unixctl_client *client; + int error = unixctl_client_create(path, &client); if (error) { fprintf(stderr, "Error connecting to \"%s\": %s\n", path, strerror(error)); @@ -116,33 +132,10 @@ add_target(struct vlog_client ***clients, size_t *n_clients, } } -static void -add_all_targets(struct vlog_client ***clients, size_t *n_clients, bool *ok) -{ - DIR *directory; - struct dirent* de; - - directory = opendir("/tmp"); - if (!directory) { - fprintf(stderr, "/tmp: opendir: %s\n", strerror(errno)); - } - - while ((de = readdir(directory)) != NULL) { - if (!strncmp(de->d_name, "vlogs.", 5)) { - char *path = xasprintf("/tmp/%s", de->d_name); - add_target(clients, n_clients, path, ok); - free(path); - } - } - - closedir(directory); -} - int main(int argc, char *argv[]) { static const struct option long_options[] = { /* Target options must come first. */ - {"all", no_argument, NULL, 'a'}, {"target", required_argument, NULL, 't'}, {"help", no_argument, NULL, 'h'}, @@ -150,6 +143,7 @@ int main(int argc, char *argv[]) {"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; @@ -157,7 +151,7 @@ int main(int argc, char *argv[]) /* Determine targets. */ bool ok = true; int n_actions = 0; - struct vlog_client **clients = NULL; + struct unixctl_client **clients = NULL; size_t n_clients = 0; set_program_name(argv[0]); @@ -172,27 +166,23 @@ int main(int argc, char *argv[]) if (option == -1) { break; } - if (!strchr("ath", option) && n_clients == 0) { + if (!strchr("th", option) && n_clients == 0) { ofp_fatal(0, "no targets specified (use --help for help)"); } else { ++n_actions; } switch (option) { - case 'a': - add_all_targets(&clients, &n_clients, &ok); - break; - case 't': add_target(&clients, &n_clients, optarg, &ok); break; case 'l': for (i = 0; i < n_clients; i++) { - struct vlog_client *client = clients[i]; + struct unixctl_client *client = clients[i]; char *reply; - printf("%s:\n", vlog_client_target(client)); - reply = transact(client, "list", &ok); + printf("%s:\n", unixctl_client_target(client)); + reply = transact(client, "vlog/list", &ok); fputs(reply, stdout); free(reply); } @@ -200,8 +190,8 @@ int main(int argc, char *argv[]) case 's': for (i = 0; i < n_clients; i++) { - struct vlog_client *client = clients[i]; - char *request = xasprintf("set %s", optarg); + struct unixctl_client *client = clients[i]; + char *request = xasprintf("vlog/set %s", optarg); transact_ack(client, request, &ok); free(request); } @@ -209,13 +199,19 @@ int main(int argc, char *argv[]) case 'r': for (i = 0; i < n_clients; i++) { - struct vlog_client *client = clients[i]; - char *request = xstrdup("reopen"); + 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; diff --git a/vswitchd/brcompatd.c b/vswitchd/brcompatd.c index 8050b0e4..5fa0fd97 100644 --- a/vswitchd/brcompatd.c +++ b/vswitchd/brcompatd.c @@ -49,8 +49,8 @@ #include "signals.h" #include "svec.h" #include "timeval.h" +#include "unixctl.h" #include "util.h" -#include "vlog-socket.h" #include "vlog.h" #define THIS_MODULE VLM_brcompatd @@ -559,6 +559,7 @@ rtnl_recv_update(void) int main(int argc, char *argv[]) { + struct unixctl_server *unixctl; int retval; set_program_name(argv[0]); @@ -574,7 +575,7 @@ main(int argc, char *argv[]) fatal_signal_add_hook(release_lock, NULL, true); - retval = vlog_server_listen(NULL, NULL); + retval = unixctl_server_create(NULL, &unixctl); if (retval) { ofp_fatal(retval, "could not listen for vlog connections"); } @@ -599,6 +600,7 @@ main(int argc, char *argv[]) cfg_read(); for (;;) { + unixctl_server_run(unixctl); brc_recv_update(); /* If 'prune_timeout' is non-zero, we actively prune from the @@ -620,7 +622,7 @@ main(int argc, char *argv[]) } nl_sock_wait(brc_sock, POLLIN); - + unixctl_server_wait(unixctl); poll_block(); } diff --git a/vswitchd/vswitchd.c b/vswitchd/vswitchd.c index fc216168..e5cedcb9 100644 --- a/vswitchd/vswitchd.c +++ b/vswitchd/vswitchd.c @@ -48,10 +48,10 @@ #include "signals.h" #include "svec.h" #include "timeval.h" +#include "unixctl.h" #include "util.h" #include "vconn-ssl.h" #include "vconn.h" -#include "vlog-socket.h" #include "vswitchd.h" #include "vlog.h" @@ -65,6 +65,7 @@ char *config_file; int main(int argc, char *argv[]) { + struct unixctl_server *unixctl; struct signal *sighup; bool need_reconfigure; int retval; @@ -81,9 +82,9 @@ main(int argc, char *argv[]) die_if_already_running(); daemonize(); - retval = vlog_server_listen(NULL, NULL); + retval = unixctl_server_create(NULL, &unixctl); if (retval) { - ofp_fatal(retval, "could not listen for vlog connections"); + ofp_fatal(retval, "could not listen for control connections"); } cfg_read(); @@ -103,10 +104,12 @@ main(int argc, char *argv[]) if (bridge_run()) { need_reconfigure = true; } + unixctl_server_run(unixctl); signal_wait(sighup); mgmt_wait(); bridge_wait(); + unixctl_server_wait(unixctl); poll_block(); }