Use signal-based timekeeping functions throughout the source base.
[sliver-openvswitch.git] / lib / vlog-socket.c
1 /* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
2  * Junior University
3  * 
4  * We are making the OpenFlow specification and associated documentation
5  * (Software) available for public use and benefit with the expectation
6  * that others will use, modify and enhance the Software and contribute
7  * those enhancements back to the community. However, since we would
8  * like to make the Software available for broadest use, with as few
9  * restrictions as possible permission is hereby granted, free of
10  * charge, to any person obtaining a copy of this Software to deal in
11  * the Software under the copyrights without restriction, including
12  * without limitation the rights to use, copy, modify, merge, publish,
13  * distribute, sublicense, and/or sell copies of the Software, and to
14  * permit persons to whom the Software is furnished to do so, subject to
15  * the following conditions:
16  * 
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  * 
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
24  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
25  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27  * SOFTWARE.
28  * 
29  * The name and trademarks of copyright holder(s) may NOT be used in
30  * advertising or publicity pertaining to the Software or any
31  * derivatives without specific, written prior permission.
32  */
33
34 #include <config.h>
35 #include "vlog-socket.h"
36 #include <errno.h>
37 #include <sys/un.h>
38 #include <fcntl.h>
39 #include <poll.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/types.h>
45 #include <unistd.h>
46 #include "fatal-signal.h"
47 #include "poll-loop.h"
48 #include "socket-util.h"
49 #include "util.h"
50 #include "vlog.h"
51
52 #ifndef SCM_CREDENTIALS
53 #include <time.h>
54 #endif
55 \f
56 /* Server for Vlog control connection. */
57 struct vlog_server {
58     struct poll_waiter *waiter;
59     char *path;
60     int fd;
61 };
62
63 static void poll_server(int fd, short int events, void *server_);
64
65 /* Start listening for connections from clients and processing their
66  * requests.  'path' may be:
67  *
68  *      - NULL, in which case the default socket path is used.  (Only one
69  *        Vlog_server_socket per process can use the default path.)
70  *
71  *      - A name that does not start with '/', in which case it is appended to
72  *        the default socket path.
73  *
74  *      - An absolute path (starting with '/') that gives the exact name of
75  *        the Unix domain socket to listen on.
76  *
77  * Returns 0 if successful, otherwise a positive errno value.  If successful,
78  * sets '*serverp' to the new vlog_server, otherwise to NULL. */
79 int
80 vlog_server_listen(const char *path, struct vlog_server **serverp)
81 {
82     struct vlog_server *server = xmalloc(sizeof *server);
83
84     if (path && path[0] == '/') {
85         server->path = xstrdup(path);
86     } else {
87         server->path = xasprintf("/tmp/vlogs.%ld%s",
88                                  (long int) getpid(), path ? path : "");
89     }
90
91     server->fd = make_unix_socket(SOCK_DGRAM, true, true, server->path, NULL);
92     if (server->fd < 0) {
93         int fd = server->fd;
94         free(server->path);
95         free(server);
96         fprintf(stderr, "Could not initialize vlog configuration socket: %s\n",
97                 strerror(-server->fd));
98         if (serverp) {
99             *serverp = NULL; 
100         }
101         return fd;
102     }
103
104     server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
105
106     if (serverp) {
107         *serverp = server; 
108     }
109     return 0;
110 }
111
112 /* Destroys 'server' and stops listening for connections. */
113 void
114 vlog_server_close(struct vlog_server *server)
115 {
116     if (server) {
117         poll_cancel(server->waiter);
118         close(server->fd);
119         unlink(server->path);
120         fatal_signal_remove_file_to_unlink(server->path);
121         free(server->path);
122         free(server);
123     }
124 }
125
126 static int
127 recv_with_creds(const struct vlog_server *server,
128                 char *cmd_buf, size_t cmd_buf_size,
129                 struct sockaddr_un *un, socklen_t *un_len)
130 {
131 #ifdef SCM_CREDENTIALS
132     /* Read a message and control messages from 'fd'.  */
133     char cred_buf[CMSG_SPACE(sizeof(struct ucred))];
134     ssize_t n;
135     struct iovec iov;
136     struct msghdr msg;
137     struct ucred* cred;
138     struct cmsghdr* cmsg;
139
140     iov.iov_base = cmd_buf;
141     iov.iov_len = cmd_buf_size - 1;
142
143     memset(&msg, 0, sizeof msg);
144     msg.msg_name = un;
145     msg.msg_namelen = sizeof *un;
146     msg.msg_iov = &iov;
147     msg.msg_iovlen = 1;
148     msg.msg_control = cred_buf;
149     msg.msg_controllen = sizeof cred_buf;
150
151     n = recvmsg(server->fd, &msg, 0);
152     *un_len = msg.msg_namelen;
153     if (n < 0) {
154         return errno;
155     }
156     cmd_buf[n] = '\0';
157
158     /* Ensure that the message has credentials ensuring that it was sent
159      * from the same user who started us, or by root. */
160     cred = NULL;
161     for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
162          cmsg = CMSG_NXTHDR(&msg, cmsg)) {
163         if (cmsg->cmsg_level == SOL_SOCKET
164             && cmsg->cmsg_type == SCM_CREDENTIALS) {
165             cred = (struct ucred *) CMSG_DATA(cmsg);
166         } else if (cmsg->cmsg_level == SOL_SOCKET
167                    && cmsg->cmsg_type == SCM_RIGHTS) {
168             /* Anyone can send us fds.  If we don't close them, then that's
169              * a DoS: the sender can overflow our fd table. */
170             int* fds = (int *) CMSG_DATA(cmsg);
171             size_t n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof *fds;
172             size_t i;
173             for (i = 0; i < n_fds; i++) {
174                 close(fds[i]);
175             }
176         }
177     }
178     if (!cred) {
179         fprintf(stderr, "vlog: config message lacks credentials\n");
180         return -1;
181     } else if (cred->uid && cred->uid != getuid()) {
182         fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
183                 (long int) cred->uid, (long int) getuid());
184         return -1;
185     }
186
187     return 0;
188 #else /* !SCM_CREDENTIALS */
189     socklen_t len;
190     ssize_t n;
191     struct stat s;
192     time_t recent;
193
194     /* Receive a message. */
195     len = sizeof *un;
196     n = recvfrom(server->fd, cmd_buf, cmd_buf_size - 1, 0,
197                  (struct sockaddr *) un, &len);
198     *un_len = len;
199     if (n < 0) {
200         return errno;
201     }
202     cmd_buf[n] = '\0';
203
204     len -= offsetof(struct sockaddr_un, sun_path);
205     un->sun_path[len] = '\0';
206     if (stat(un->sun_path, &s) < 0) {
207         fprintf(stderr, "vlog: config message from inaccessible socket: %s\n",
208                 strerror(errno));
209         return -1;
210     }
211     if (!S_ISSOCK(s.st_mode)) {
212         fprintf(stderr, "vlog: config message not from a socket\n");
213         return -1;
214     }
215     recent = time_now() - 30;
216     if (s.st_atime < recent || s.st_ctime < recent || s.st_mtime < recent) {
217         fprintf(stderr, "vlog: config socket too old\n");
218         return -1;
219     }
220     if (s.st_uid && s.st_uid != getuid()) {
221         fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
222                 (long int) s.st_uid, (long int) getuid());
223         return -1;
224     }
225     return 0;
226 #endif /* !SCM_CREDENTIALS */
227 }
228
229 /* Processes incoming requests for 'server'. */
230 static void
231 poll_server(int fd UNUSED, short int events, void *server_)
232 {
233     struct vlog_server *server = server_;
234     for (;;) {
235         char cmd_buf[512];
236         struct sockaddr_un un;
237         socklen_t un_len;
238         char *reply;
239         int error;
240
241         error = recv_with_creds(server, cmd_buf, sizeof cmd_buf, &un, &un_len);
242         if (error > 0) {
243             if (error != EAGAIN && error != EWOULDBLOCK) {
244                 fprintf(stderr, "vlog: reading configuration socket: %s",
245                         strerror(errno));
246             }
247             break;
248         } else if (error < 0) {
249             continue;
250         }
251
252         /* Process message and send reply. */
253         if (!strncmp(cmd_buf, "set ", 4)) {
254             char *msg = vlog_set_levels_from_string(cmd_buf + 4);
255             reply = msg ? msg : xstrdup("ack");
256         } else if (!strcmp(cmd_buf, "list")) {
257             reply = vlog_get_levels();
258         } else {
259             reply = xstrdup("nak");
260         }
261         sendto(server->fd, reply, strlen(reply), 0,
262                (struct sockaddr*) &un, un_len);
263         free(reply);
264     }
265     server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
266 }
267 \f
268 /* Client for Vlog control connection. */
269
270 struct vlog_client {
271     char *connect_path;
272     char *bind_path;
273     int fd;
274 };
275
276 /* Connects to a Vlog server socket.  If 'path' does not start with '/', then
277  * it start with a PID as a string.  If a non-null, non-absolute name was
278  * passed to Vlog_server_socket::listen(), then it must follow the PID in
279  * 'path'.  If 'path' starts with '/', then it must be an absolute path that
280  * gives the exact name of the Unix domain socket to connect to.
281  *
282  * Returns 0 if successful, otherwise a positive errno value.  If successful,
283  * sets '*clientp' to the new vlog_client, otherwise to NULL. */
284 int
285 vlog_client_connect(const char *path, struct vlog_client **clientp)
286 {
287     struct vlog_client *client;
288     int fd;
289
290     client = xmalloc(sizeof *client);
291     client->connect_path = (path[0] == '/'
292                             ? xstrdup(path)
293                             : xasprintf("/tmp/vlogs.%s", path));
294
295     client->bind_path = xasprintf("/tmp/vlog.%ld", (long int) getpid());
296     fd = make_unix_socket(SOCK_DGRAM, false, false,
297                           client->bind_path, client->connect_path);
298
299     if (fd >= 0) {
300         client->fd = fd;
301         *clientp = client;
302         return 0;
303     } else {
304         free(client->connect_path);
305         free(client->bind_path);
306         free(client);
307         *clientp = NULL;
308         return errno;
309     }
310 }
311
312 /* Destroys 'client'. */
313 void
314 vlog_client_close(struct vlog_client *client)
315 {
316     if (client) {
317         unlink(client->bind_path);
318         fatal_signal_remove_file_to_unlink(client->bind_path);
319         free(client->bind_path);
320         free(client->connect_path);
321         close(client->fd);
322         free(client);
323     }
324 }
325
326 /* Sends 'request' to the server socket that 'client' is connected to.  Returns
327  * 0 if successful, otherwise a positive errno value. */
328 int
329 vlog_client_send(struct vlog_client *client, const char *request)
330 {
331 #ifdef SCM_CREDENTIALS
332     struct ucred cred;
333     struct iovec iov;
334     char buf[CMSG_SPACE(sizeof cred)];
335     struct msghdr msg;
336     struct cmsghdr* cmsg;
337     ssize_t nbytes;
338
339     cred.pid = getpid();
340     cred.uid = getuid();
341     cred.gid = getgid();
342
343     iov.iov_base = (void*) request;
344     iov.iov_len = strlen(request);
345
346     memset(&msg, 0, sizeof msg);
347     msg.msg_iov = &iov;
348     msg.msg_iovlen = 1;
349     msg.msg_control = buf;
350     msg.msg_controllen = sizeof buf;
351
352     cmsg = CMSG_FIRSTHDR(&msg);
353     cmsg->cmsg_level = SOL_SOCKET;
354     cmsg->cmsg_type = SCM_CREDENTIALS;
355     cmsg->cmsg_len = CMSG_LEN(sizeof cred);
356     memcpy(CMSG_DATA(cmsg), &cred, sizeof cred);
357     msg.msg_controllen = cmsg->cmsg_len;
358
359     nbytes = sendmsg(client->fd, &msg, 0);
360 #else /* !SCM_CREDENTIALS */
361     ssize_t nbytes = send(client->fd, request, strlen(request), 0);
362 #endif /* !SCM_CREDENTIALS */
363     if (nbytes > 0) {
364         return nbytes == strlen(request) ? 0 : ENOBUFS;
365     } else {
366         return errno;
367     }
368 }
369
370 /* Attempts to receive a response from the server socket that 'client' is
371  * connected to.  Returns 0 if successful, otherwise a positive errno value.
372  * If successful, sets '*reply' to the reply, which the caller must free,
373  * otherwise to NULL. */
374 int
375 vlog_client_recv(struct vlog_client *client, char **reply)
376 {
377     struct pollfd pfd;
378     int nfds;
379     char buffer[65536];
380     ssize_t nbytes;
381
382     *reply = NULL;
383
384     pfd.fd = client->fd;
385     pfd.events = POLLIN;
386     nfds = poll(&pfd, 1, 1000);
387     if (nfds == 0) {
388         return ETIMEDOUT;
389     } else if (nfds < 0) {
390         return errno;
391     }
392
393     nbytes = read(client->fd, buffer, sizeof buffer - 1);
394     if (nbytes < 0) {
395         return errno;
396     } else {
397         buffer[nbytes] = '\0';
398         *reply = xstrdup(buffer);
399         return 0;
400     }
401 }
402
403 /* Sends 'request' to the server socket and waits for a reply.  Returns 0 if
404  * successful, otherwise to a positive errno value.  If successful, sets
405  * '*reply' to the reply, which the caller must free, otherwise to NULL. */
406 int
407 vlog_client_transact(struct vlog_client *client,
408                      const char *request, char **reply)
409 {
410     int i;
411
412     /* Retry up to 3 times. */
413     for (i = 0; i < 3; ++i) {
414         int error = vlog_client_send(client, request);
415         if (error) {
416             *reply = NULL;
417             return error;
418         }
419         error = vlog_client_recv(client, reply);
420         if (error != ETIMEDOUT) {
421             return error;
422         }
423     }
424     *reply = NULL;
425     return ETIMEDOUT;
426 }
427
428 /* Returns the path of the server socket to which 'client' is connected.  The
429  * caller must not modify or free the returned string. */
430 const char *
431 vlog_client_target(const struct vlog_client *client)
432 {
433     return client->connect_path;
434 }
435 \f