483 lines
15 KiB
C
483 lines
15 KiB
C
/* 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 <config.h>
|
|
#include "vlog-socket.h"
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <sys/un.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#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 <time.h>
|
|
#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 <program>.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;
|
|
}
|