CHECKSUM_COMPLETE and CHECKSUM_PARTIAL were added Linux 2.6.19, so update the compati...
[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 "vlog-socket.h"
35 #include <errno.h>
36 #include <sys/un.h>
37 #include <fcntl.h>
38 #include <poll.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/socket.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <unistd.h>
45 #include "fatal-signal.h"
46 #include "poll-loop.h"
47 #include "util.h"
48 #include "vlog.h"
49
50 #ifndef SCM_CREDENTIALS
51 #include <time.h>
52 #endif
53
54 static int make_unix_socket(bool nonblock, bool passcred,
55                             const char *bind_path, const char *connect_path);
56 \f
57 /* Server for Vlog control connection. */
58 struct vlog_server {
59     struct poll_waiter *waiter;
60     char *path;
61     int fd;
62 };
63
64 static void poll_server(int fd, short int events, void *server_);
65
66 /* Start listening for connections from clients and processing their
67  * requests.  'path' may be:
68  *
69  *      - NULL, in which case the default socket path is used.  (Only one
70  *        Vlog_server_socket per process can use the default path.)
71  *
72  *      - A name that does not start with '/', in which case it is appended to
73  *        the default socket path.
74  *
75  *      - An absolute path (starting with '/') that gives the exact name of
76  *        the Unix domain socket to listen on.
77  *
78  * Returns 0 if successful, otherwise a positive errno value.  If successful,
79  * sets '*serverp' to the new vlog_server, otherwise to NULL. */
80 int
81 vlog_server_listen(const char *path, struct vlog_server **serverp)
82 {
83     struct vlog_server *server = xmalloc(sizeof *server);
84
85     if (path && path[0] == '/') {
86         server->path = xstrdup(path);
87     } else {
88         server->path = xasprintf("/tmp/vlogs.%ld%s",
89                                  (long int) getpid(), path ? path : "");
90     }
91
92     server->fd = make_unix_socket(true, true, server->path, NULL);
93     if (server->fd < 0) {
94         int fd = server->fd;
95         free(server->path);
96         free(server);
97         fprintf(stderr, "Could not initialize vlog configuration socket: %s\n",
98                 strerror(-server->fd));
99         if (serverp) {
100             *serverp = NULL; 
101         }
102         return fd;
103     }
104
105     server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
106
107     if (serverp) {
108         *serverp = server; 
109     }
110     return 0;
111 }
112
113 /* Destroys 'server' and stops listening for connections. */
114 void
115 vlog_server_close(struct vlog_server *server)
116 {
117     if (server) {
118         poll_cancel(server->waiter);
119         close(server->fd);
120         unlink(server->path);
121         fatal_signal_remove_file_to_unlink(server->path);
122         free(server->path);
123         free(server);
124     }
125 }
126
127 static int
128 recv_with_creds(const struct vlog_server *server,
129                 char *cmd_buf, size_t cmd_buf_size,
130                 struct sockaddr_un *un, socklen_t *un_len)
131 {
132 #ifdef SCM_CREDENTIALS
133     /* Read a message and control messages from 'fd'.  */
134     char cred_buf[CMSG_SPACE(sizeof(struct ucred))];
135     ssize_t n;
136     struct iovec iov;
137     struct msghdr msg;
138     struct ucred* cred;
139     struct cmsghdr* cmsg;
140
141     iov.iov_base = cmd_buf;
142     iov.iov_len = cmd_buf_size - 1;
143
144     memset(&msg, 0, sizeof msg);
145     msg.msg_name = un;
146     msg.msg_namelen = sizeof *un;
147     msg.msg_iov = &iov;
148     msg.msg_iovlen = 1;
149     msg.msg_control = cred_buf;
150     msg.msg_controllen = sizeof cred_buf;
151
152     n = recvmsg(server->fd, &msg, 0);
153     *un_len = msg.msg_namelen;
154     if (n < 0) {
155         return errno;
156     }
157     cmd_buf[n] = '\0';
158
159     /* Ensure that the message has credentials ensuring that it was sent
160      * from the same user who started us, or by root. */
161     cred = NULL;
162     for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
163          cmsg = CMSG_NXTHDR(&msg, cmsg)) {
164         if (cmsg->cmsg_level == SOL_SOCKET
165             && cmsg->cmsg_type == SCM_CREDENTIALS) {
166             cred = (struct ucred *) CMSG_DATA(cmsg);
167         } else if (cmsg->cmsg_level == SOL_SOCKET
168                    && cmsg->cmsg_type == SCM_RIGHTS) {
169             /* Anyone can send us fds.  If we don't close them, then that's
170              * a DoS: the sender can overflow our fd table. */
171             int* fds = (int *) CMSG_DATA(cmsg);
172             size_t n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof *fds;
173             size_t i;
174             for (i = 0; i < n_fds; i++) {
175                 close(fds[i]);
176             }
177         }
178     }
179     if (!cred) {
180         fprintf(stderr, "vlog: config message lacks credentials\n");
181         return -1;
182     } else if (cred->uid && cred->uid != getuid()) {
183         fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
184                 (long int) cred->uid, (long int) getuid());
185         return -1;
186     }
187
188     return 0;
189 #else /* !SCM_CREDENTIALS */
190     socklen_t len;
191     ssize_t n;
192     struct stat s;
193     time_t recent;
194
195     /* Receive a message. */
196     len = sizeof *un;
197     n = recvfrom(server->fd, cmd_buf, cmd_buf_size - 1, 0,
198                  (struct sockaddr *) un, &len);
199     *un_len = len;
200     if (n < 0) {
201         return errno;
202     }
203     cmd_buf[n] = '\0';
204
205     len -= offsetof(struct sockaddr_un, sun_path);
206     un->sun_path[len] = '\0';
207     if (stat(un->sun_path, &s) < 0) {
208         fprintf(stderr, "vlog: config message from inaccessible socket: %s\n",
209                 strerror(errno));
210         return -1;
211     }
212     if (!S_ISSOCK(s.st_mode)) {
213         fprintf(stderr, "vlog: config message not from a socket\n");
214         return -1;
215     }
216     recent = time(0) - 30;
217     if (s.st_atime < recent || s.st_ctime < recent || s.st_mtime < recent) {
218         fprintf(stderr, "vlog: config socket too old\n");
219         return -1;
220     }
221     if (s.st_uid && s.st_uid != getuid()) {
222         fprintf(stderr, "vlog: config message uid=%ld is not 0 or %ld\n",
223                 (long int) s.st_uid, (long int) getuid());
224         return -1;
225     }
226     return 0;
227 #endif /* !SCM_CREDENTIALS */
228 }
229
230 /* Processes incoming requests for 'server'. */
231 static void
232 poll_server(int fd UNUSED, short int events, void *server_)
233 {
234     struct vlog_server *server = server_;
235     for (;;) {
236         char cmd_buf[512];
237         struct sockaddr_un un;
238         socklen_t un_len;
239         char *reply;
240         int error;
241
242         error = recv_with_creds(server, cmd_buf, sizeof cmd_buf, &un, &un_len);
243         if (error > 0) {
244             if (error != EAGAIN && error != EWOULDBLOCK) {
245                 fprintf(stderr, "vlog: reading configuration socket: %s",
246                         strerror(errno));
247             }
248             break;
249         } else if (error < 0) {
250             continue;
251         }
252
253         /* Process message and send reply. */
254         if (!strncmp(cmd_buf, "set ", 4)) {
255             char *msg = vlog_set_levels_from_string(cmd_buf + 4);
256             reply = msg ? msg : xstrdup("ack");
257         } else if (!strcmp(cmd_buf, "list")) {
258             reply = vlog_get_levels();
259         } else {
260             reply = xstrdup("nak");
261         }
262         sendto(server->fd, reply, strlen(reply), 0,
263                (struct sockaddr*) &un, un_len);
264         free(reply);
265     }
266     server->waiter = poll_fd_callback(server->fd, POLLIN, poll_server, server);
267 }
268 \f
269 /* Client for Vlog control connection. */
270
271 struct vlog_client {
272     char *connect_path;
273     char *bind_path;
274     int fd;
275 };
276
277 /* Connects to a Vlog server socket.  If 'path' does not start with '/', then
278  * it start with a PID as a string.  If a non-null, non-absolute name was
279  * passed to Vlog_server_socket::listen(), then it must follow the PID in
280  * 'path'.  If 'path' starts with '/', then it must be an absolute path that
281  * gives the exact name of the Unix domain socket to connect to.
282  *
283  * Returns 0 if successful, otherwise a positive errno value.  If successful,
284  * sets '*clientp' to the new vlog_client, otherwise to NULL. */
285 int
286 vlog_client_connect(const char *path, struct vlog_client **clientp)
287 {
288     struct vlog_client *client;
289     int fd;
290
291     client = xmalloc(sizeof *client);
292     client->connect_path = (path[0] == '/'
293                             ? xstrdup(path)
294                             : xasprintf("/tmp/vlogs.%s", path));
295
296     client->bind_path = xasprintf("/tmp/vlog.%ld", (long int) getpid());
297     fd = make_unix_socket(false, false,
298                           client->bind_path, client->connect_path);
299
300     if (fd >= 0) {
301         client->fd = fd;
302         *clientp = client;
303         return 0;
304     } else {
305         free(client->connect_path);
306         free(client->bind_path);
307         free(client);
308         *clientp = NULL;
309         return errno;
310     }
311 }
312
313 /* Destroys 'client'. */
314 void
315 vlog_client_close(struct vlog_client *client)
316 {
317     if (client) {
318         unlink(client->bind_path);
319         fatal_signal_remove_file_to_unlink(client->bind_path);
320         free(client->bind_path);
321         free(client->connect_path);
322         close(client->fd);
323         free(client);
324     }
325 }
326
327 /* Sends 'request' to the server socket that 'client' is connected to.  Returns
328  * 0 if successful, otherwise a positive errno value. */
329 int
330 vlog_client_send(struct vlog_client *client, const char *request)
331 {
332 #ifdef SCM_CREDENTIALS
333     struct ucred cred;
334     struct iovec iov;
335     char buf[CMSG_SPACE(sizeof cred)];
336     struct msghdr msg;
337     struct cmsghdr* cmsg;
338     ssize_t nbytes;
339
340     cred.pid = getpid();
341     cred.uid = getuid();
342     cred.gid = getgid();
343
344     iov.iov_base = (void*) request;
345     iov.iov_len = strlen(request);
346
347     memset(&msg, 0, sizeof msg);
348     msg.msg_iov = &iov;
349     msg.msg_iovlen = 1;
350     msg.msg_control = buf;
351     msg.msg_controllen = sizeof buf;
352
353     cmsg = CMSG_FIRSTHDR(&msg);
354     cmsg->cmsg_level = SOL_SOCKET;
355     cmsg->cmsg_type = SCM_CREDENTIALS;
356     cmsg->cmsg_len = CMSG_LEN(sizeof cred);
357     memcpy(CMSG_DATA(cmsg), &cred, sizeof cred);
358     msg.msg_controllen = cmsg->cmsg_len;
359
360     nbytes = sendmsg(client->fd, &msg, 0);
361 #else /* !SCM_CREDENTIALS */
362     ssize_t nbytes = send(client->fd, request, strlen(request), 0);
363 #endif /* !SCM_CREDENTIALS */
364     if (nbytes > 0) {
365         return nbytes == strlen(request) ? 0 : ENOBUFS;
366     } else {
367         return errno;
368     }
369 }
370
371 /* Attempts to receive a response from the server socket that 'client' is
372  * connected to.  Returns 0 if successful, otherwise a positive errno value.
373  * If successful, sets '*reply' to the reply, which the caller must free,
374  * otherwise to NULL. */
375 int
376 vlog_client_recv(struct vlog_client *client, char **reply)
377 {
378     struct pollfd pfd;
379     int nfds;
380     char buffer[65536];
381     ssize_t nbytes;
382
383     *reply = NULL;
384
385     pfd.fd = client->fd;
386     pfd.events = POLLIN;
387     nfds = poll(&pfd, 1, 1000);
388     if (nfds == 0) {
389         return ETIMEDOUT;
390     } else if (nfds < 0) {
391         return errno;
392     }
393
394     nbytes = read(client->fd, buffer, sizeof buffer - 1);
395     if (nbytes < 0) {
396         return errno;
397     } else {
398         buffer[nbytes] = '\0';
399         *reply = xstrdup(buffer);
400         return 0;
401     }
402 }
403
404 /* Sends 'request' to the server socket and waits for a reply.  Returns 0 if
405  * successful, otherwise to a positive errno value.  If successful, sets
406  * '*reply' to the reply, which the caller must free, otherwise to NULL. */
407 int
408 vlog_client_transact(struct vlog_client *client,
409                      const char *request, char **reply)
410 {
411     int i;
412
413     /* Retry up to 3 times. */
414     for (i = 0; i < 3; ++i) {
415         int error = vlog_client_send(client, request);
416         if (error) {
417             *reply = NULL;
418             return error;
419         }
420         error = vlog_client_recv(client, reply);
421         if (error != ETIMEDOUT) {
422             return error;
423         }
424     }
425     *reply = NULL;
426     return ETIMEDOUT;
427 }
428
429 /* Returns the path of the server socket to which 'client' is connected.  The
430  * caller must not modify or free the returned string. */
431 const char *
432 vlog_client_target(const struct vlog_client *client)
433 {
434     return client->connect_path;
435 }
436 \f
437 /* Helper functions. */
438
439 /* Stores in '*un' a sockaddr_un that refers to file 'name'.  Stores in
440  * '*un_len' the size of the sockaddr_un. */
441 static void
442 make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len)
443 {
444     un->sun_family = AF_UNIX;
445     strncpy(un->sun_path, name, sizeof un->sun_path);
446     un->sun_path[sizeof un->sun_path - 1] = '\0';
447     *un_len = (offsetof(struct sockaddr_un, sun_path)
448                 + strlen (un->sun_path) + 1);
449 }
450
451 /* Creates a Unix domain datagram socket that is bound to '*bind_path' (if
452  * 'bind_path' is non-null) and connected to '*connect_path' (if 'connect_path'
453  * is non-null).  If 'nonblock' is true, the socket is made non-blocking.  If
454  * 'passcred' is true, the socket is configured to receive SCM_CREDENTIALS
455  * control messages.
456  *
457  * Returns the socket's fd if successful, otherwise a negative errno value. */
458 static int
459 make_unix_socket(bool nonblock, bool passcred UNUSED,
460                  const char *bind_path, const char *connect_path)
461 {
462     int error;
463     int fd;
464
465     fd = socket(PF_UNIX, SOCK_DGRAM, 0);
466     if (fd < 0) {
467         return -errno;
468     }
469
470     if (nonblock) {
471         int flags = fcntl(fd, F_GETFL, 0);
472         if (flags == -1) {
473             goto error;
474         }
475         if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
476             goto error;
477         }
478     }
479
480     if (bind_path) {
481         struct sockaddr_un un;
482         socklen_t un_len;
483         make_sockaddr_un(bind_path, &un, &un_len);
484         if (unlink(un.sun_path) && errno != ENOENT) {
485             fprintf(stderr, "unlinking \"%s\": %s\n",
486                     un.sun_path, strerror(errno));
487         }
488         fatal_signal_add_file_to_unlink(bind_path);
489         if (bind(fd, (struct sockaddr*) &un, un_len)
490             || fchmod(fd, S_IRWXU)) {
491             goto error;
492         }
493     }
494
495     if (connect_path) {
496         struct sockaddr_un un;
497         socklen_t un_len;
498         make_sockaddr_un(connect_path, &un, &un_len);
499         if (connect(fd, (struct sockaddr*) &un, un_len)) {
500             goto error;
501         }
502     }
503
504 #ifdef SCM_CREDENTIALS
505     if (passcred) {
506         int enable = 1;
507         if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable))) {
508             goto error;
509         }
510     }
511 #endif
512
513     return fd;
514
515 error:
516     if (bind_path) {
517         fatal_signal_remove_file_to_unlink(bind_path);
518     }
519     error = errno;
520     close(fd);
521     return -error;
522 }