/* 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 #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, 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; }