1 /**
2  * @file
3  * @section AUTHORS
4  *
5  * Copyright (C) 2010  Rafal Wojtczuk  <rafal@invisiblethingslab.com>
6  *
7  *  Authors:
8  *       Rafal Wojtczuk  <rafal@invisiblethingslab.com>
9  *       Daniel De Graaf <dgdegra@tycho.nsa.gov>
10  *       Marek Marczykowski-Górecki  <marmarek@invisiblethingslab.com>
11  *
12  * @section LICENSE
13  *
14  *  This program is free software; you can redistribute it and/or
15  *  modify it under the terms of the GNU Lesser General Public
16  *  License as published by the Free Software Foundation; either
17  *  version 2.1 of the License, or (at your option) any later version.
18  *
19  *  This program is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  *  Lesser General Public License for more details.
23  *
24  *  You should have received a copy of the GNU Lesser General Public
25  *  License along with this program; If not, see <http://www.gnu.org/licenses/>.
26  *
27  * @section DESCRIPTION
28  *
29  * This is a vchan to unix socket proxy. Vchan server is set, and on client
30  * connection, local socket connection is established. Communication is bidirectional.
31  * One client is served at a time, clients needs to coordinate this themselves.
32  */
33 
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <fcntl.h>
39 #include <errno.h>
40 #include <sys/socket.h>
41 #include <sys/un.h>
42 #include <getopt.h>
43 
44 #include <xenstore.h>
45 #include <xenctrl.h>
46 #include <libxenvchan.h>
47 
usage(char ** argv)48 static void usage(char** argv)
49 {
50     fprintf(stderr, "usage:\n"
51         "\t%s [options] domainid nodepath [socket-path|file-no|-]\n"
52         "\n"
53         "options:\n"
54         "\t-m, --mode=client|server - vchan connection mode (client by default)\n"
55         "\t-s, --state-path=path - xenstore path where write \"running\" to \n"
56         "\t                        at startup\n"
57         "\t-v, --verbose - verbose logging\n"
58         "\n"
59         "client: client of a vchan connection, fourth parameter can be:\n"
60         "\tsocket-path: listen on a UNIX socket at this path and connect to vchan\n"
61         "\t             whenever new connection is accepted;\n"
62         "\t             handle multiple _subsequent_ connections, until terminated\n"
63         "\n"
64         "\tfile-no:     except open FD of a socket in listen mode;\n"
65         "\t             otherwise similar to socket-path\n"
66         "\n"
67         "\t-:           open vchan connection immediately and pass the data\n"
68         "\t             from stdin/stdout; terminate when vchan connection\n"
69         "\t             is closed\n"
70         "\n"
71         "server: server of a vchan connection, fourth parameter can be:\n"
72         "\tsocket-path: connect to this UNIX socket when new vchan connection\n"
73         "\t             is accepted;\n"
74         "\t             handle multiple _subsequent_ connections, until terminated\n"
75         "\n"
76         "\tfile-no:     pass data to/from this FD; terminate when vchan connection\n"
77         "\t             is closed\n"
78         "\n"
79         "\t-:           pass data to/from stdin/stdout; terminate when vchan\n"
80         "\t             connection is closed\n",
81         argv[0]);
82     exit(1);
83 }
84 
85 #define BUFSIZE 8192
86 char inbuf[BUFSIZE];
87 char outbuf[BUFSIZE];
88 int insiz = 0;
89 int outsiz = 0;
90 int verbose = 0;
91 
92 struct vchan_proxy_state {
93     struct libxenvchan *ctrl;
94     int output_fd;
95     int input_fd;
96 };
97 
vchan_wr(struct libxenvchan * ctrl)98 static void vchan_wr(struct libxenvchan *ctrl) {
99     int ret;
100 
101     if (!insiz)
102         return;
103     ret = libxenvchan_write(ctrl, inbuf, insiz);
104     if (ret < 0) {
105         fprintf(stderr, "vchan write failed\n");
106         exit(1);
107     }
108     if (verbose)
109         fprintf(stderr, "wrote %d bytes to vchan\n", ret);
110     if (ret > 0) {
111         insiz -= ret;
112         memmove(inbuf, inbuf + ret, insiz);
113     }
114 }
115 
socket_wr(int output_fd)116 static void socket_wr(int output_fd) {
117     int ret;
118 
119     if (!outsiz)
120         return;
121     ret = write(output_fd, outbuf, outsiz);
122     if (ret < 0 && errno != EAGAIN)
123         exit(1);
124     if (ret > 0) {
125         outsiz -= ret;
126         memmove(outbuf, outbuf + ret, outsiz);
127     }
128 }
129 
set_nonblocking(int fd,int nonblocking)130 static int set_nonblocking(int fd, int nonblocking) {
131     int flags = fcntl(fd, F_GETFL);
132     if (flags == -1)
133         return -1;
134 
135     if (nonblocking)
136         flags |= O_NONBLOCK;
137     else
138         flags &= ~O_NONBLOCK;
139 
140     if (fcntl(fd, F_SETFL, flags) == -1)
141         return -1;
142 
143     return 0;
144 }
145 
connect_socket(const char * path_or_fd)146 static int connect_socket(const char *path_or_fd) {
147     int fd;
148     char *endptr;
149     struct sockaddr_un addr;
150 
151     fd = strtoll(path_or_fd, &endptr, 0);
152     if (*endptr == '\0') {
153         set_nonblocking(fd, 1);
154         return fd;
155     }
156 
157     if (strlen(path_or_fd) >= sizeof(addr.sun_path)) {
158         fprintf(stderr, "UNIX socket path \"%s\" too long (%zd >= %zd)\n",
159                 path_or_fd, strlen(path_or_fd), sizeof(addr.sun_path));
160         return -1;
161     }
162 
163     fd = socket(AF_UNIX, SOCK_STREAM, 0);
164     if (fd == -1) {
165         perror("socket");
166         return -1;
167     }
168 
169     addr.sun_family = AF_UNIX;
170     strcpy(addr.sun_path, path_or_fd);
171     if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == -1) {
172         perror("connect");
173         close(fd);
174         return -1;
175     }
176 
177     set_nonblocking(fd, 1);
178 
179     return fd;
180 }
181 
listen_socket(const char * path_or_fd)182 static int listen_socket(const char *path_or_fd) {
183     int fd;
184     char *endptr;
185     struct sockaddr_un addr;
186 
187     fd = strtoll(path_or_fd, &endptr, 0);
188     if (*endptr == '\0') {
189         return fd;
190     }
191 
192     if (strlen(path_or_fd) >= sizeof(addr.sun_path)) {
193         fprintf(stderr, "UNIX socket path \"%s\" too long (%zd >= %zd)\n",
194                 path_or_fd, strlen(path_or_fd), sizeof(addr.sun_path));
195         return -1;
196     }
197 
198     /* if not a number, assume a socket path */
199     fd = socket(AF_UNIX, SOCK_STREAM, 0);
200     if (fd == -1) {
201         perror("socket");
202         return -1;
203     }
204 
205     addr.sun_family = AF_UNIX;
206     strcpy(addr.sun_path, path_or_fd);
207     if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) == -1) {
208         perror("bind");
209         close(fd);
210         return -1;
211     }
212     if (listen(fd, 5) != 0) {
213         perror("listen");
214         close(fd);
215         return -1;
216     }
217 
218     return fd;
219 }
220 
connect_vchan(int domid,const char * path)221 static struct libxenvchan *connect_vchan(int domid, const char *path) {
222     struct libxenvchan *ctrl = NULL;
223     struct xs_handle *xs = NULL;
224     xc_interface *xc = NULL;
225     xc_dominfo_t dominfo;
226     char **watch_ret;
227     unsigned int watch_num;
228     int ret;
229 
230     xs = xs_open(XS_OPEN_READONLY);
231     if (!xs) {
232         perror("xs_open");
233         goto out;
234     }
235     xc = xc_interface_open(NULL, NULL, XC_OPENFLAG_NON_REENTRANT);
236     if (!xc) {
237         perror("xc_interface_open");
238         goto out;
239     }
240     /* wait for vchan server to create *path* */
241     if (!xs_watch(xs, path, "path")) {
242         fprintf(stderr, "xs_watch(%s) failed.\n", path);
243         goto out;
244     }
245     if (!xs_watch(xs, "@releaseDomain", "release")) {
246         fprintf(stderr, "xs_watch(@releaseDomain failed.\n");
247         goto out;
248     }
249 
250     while ((watch_ret = xs_read_watch(xs, &watch_num))) {
251         /* don't care about exact which fired the watch */
252         free(watch_ret);
253         ctrl = libxenvchan_client_init(NULL, domid, path);
254         if (ctrl)
255             break;
256 
257         ret = xc_domain_getinfo(xc, domid, 1, &dominfo);
258         /* break the loop if domain is definitely not there anymore, but
259          * continue if it is or the call failed (like EPERM) */
260         if (ret == -1 && errno == ESRCH)
261             break;
262         if (ret == 1 && (dominfo.domid != (uint32_t)domid || dominfo.dying))
263             break;
264     }
265 
266 out:
267     if (xc)
268         xc_interface_close(xc);
269     if (xs)
270         xs_close(xs);
271     return ctrl;
272 }
273 
274 
discard_buffers(struct libxenvchan * ctrl)275 static void discard_buffers(struct libxenvchan *ctrl) {
276     /* discard local buffers */
277     insiz = 0;
278     outsiz = 0;
279 
280     /* discard remaining incoming data */
281     while (libxenvchan_data_ready(ctrl)) {
282         if (libxenvchan_read(ctrl, inbuf, BUFSIZE) == -1) {
283             perror("vchan read");
284             exit(1);
285         }
286     }
287 }
288 
data_loop(struct vchan_proxy_state * state)289 int data_loop(struct vchan_proxy_state *state)
290 {
291     int ret;
292     int libxenvchan_fd;
293     int max_fd;
294 
295     libxenvchan_fd = libxenvchan_fd_for_select(state->ctrl);
296     for (;;) {
297         fd_set rfds;
298         fd_set wfds;
299         FD_ZERO(&rfds);
300         FD_ZERO(&wfds);
301 
302         max_fd = -1;
303         if (state->input_fd != -1 && insiz != BUFSIZE) {
304             FD_SET(state->input_fd, &rfds);
305             if (state->input_fd > max_fd)
306                 max_fd = state->input_fd;
307         }
308         if (state->output_fd != -1 && outsiz) {
309             FD_SET(state->output_fd, &wfds);
310             if (state->output_fd > max_fd)
311                 max_fd = state->output_fd;
312         }
313         FD_SET(libxenvchan_fd, &rfds);
314         if (libxenvchan_fd > max_fd)
315             max_fd = libxenvchan_fd;
316         ret = select(max_fd + 1, &rfds, &wfds, NULL, NULL);
317         if (ret < 0) {
318             perror("select");
319             exit(1);
320         }
321         if (FD_ISSET(libxenvchan_fd, &rfds)) {
322             libxenvchan_wait(state->ctrl);
323             if (!libxenvchan_is_open(state->ctrl)) {
324                 if (verbose)
325                     fprintf(stderr, "vchan client disconnected\n");
326                 while (outsiz)
327                     socket_wr(state->output_fd);
328                 close(state->output_fd);
329                 state->output_fd = -1;
330                 close(state->input_fd);
331                 state->input_fd = -1;
332                 discard_buffers(state->ctrl);
333                 break;
334             }
335             vchan_wr(state->ctrl);
336         }
337 
338         if (FD_ISSET(state->input_fd, &rfds)) {
339             ret = read(state->input_fd, inbuf + insiz, BUFSIZE - insiz);
340             if (ret < 0 && errno != EAGAIN)
341                 exit(1);
342             if (verbose)
343                 fprintf(stderr, "from-unix: %.*s\n", ret, inbuf + insiz);
344             if (ret == 0) {
345                 /* EOF on socket, write everything in the buffer and close the
346                  * state->input_fd socket */
347                 while (insiz) {
348                     vchan_wr(state->ctrl);
349                     libxenvchan_wait(state->ctrl);
350                 }
351                 close(state->input_fd);
352                 if (state->input_fd == state->output_fd)
353                     state->output_fd = -1;
354                 state->input_fd = -1;
355                 /* TODO: maybe signal the vchan client somehow? */
356                 break;
357             }
358             if (ret)
359                 insiz += ret;
360             vchan_wr(state->ctrl);
361         }
362         if (FD_ISSET(state->output_fd, &wfds))
363             socket_wr(state->output_fd);
364         while (libxenvchan_data_ready(state->ctrl) && outsiz < BUFSIZE) {
365             ret = libxenvchan_read(state->ctrl, outbuf + outsiz,
366                                    BUFSIZE - outsiz);
367             if (ret < 0)
368                 exit(1);
369             if (verbose)
370                 fprintf(stderr, "from-vchan: %.*s\n", ret, outbuf + outsiz);
371             outsiz += ret;
372             socket_wr(state->output_fd);
373         }
374     }
375     return 0;
376 }
377 
378 /**
379     Simple libxenvchan application, both client and server.
380     Both sides may write and read, both from the libxenvchan and from
381     stdin/stdout (just like netcat).
382 */
383 
384 static struct option options[] = {
385     { "mode",       required_argument, NULL, 'm' },
386     { "verbose",          no_argument, NULL, 'v' },
387     { "state-path", required_argument, NULL, 's' },
388     { }
389 };
390 
main(int argc,char ** argv)391 int main(int argc, char **argv)
392 {
393     int is_server = 0;
394     int socket_fd = -1;
395     struct vchan_proxy_state state = { .ctrl = NULL,
396                                        .input_fd = -1,
397                                        .output_fd = -1 };
398     const char *socket_path;
399     int domid;
400     const char *vchan_path;
401     const char *state_path = NULL;
402     int opt;
403     int ret;
404 
405     while ((opt = getopt_long(argc, argv, "m:vs:", options, NULL)) != -1) {
406         switch (opt) {
407             case 'm':
408                 if (strcmp(optarg, "server") == 0)
409                     is_server = 1;
410                 else if (strcmp(optarg, "client") == 0)
411                     is_server = 0;
412                 else {
413                     fprintf(stderr, "invalid argument for --mode: %s\n", optarg);
414                     usage(argv);
415                     return 1;
416                 }
417                 break;
418             case 'v':
419                 verbose = 1;
420                 break;
421             case 's':
422                 state_path = optarg;
423                 break;
424             case '?':
425                 usage(argv);
426         }
427     }
428 
429     if (argc-optind != 3)
430         usage(argv);
431 
432     domid = atoi(argv[optind]);
433     vchan_path = argv[optind+1];
434     socket_path = argv[optind+2];
435 
436     if (is_server) {
437         state.ctrl = libxenvchan_server_init(NULL, domid, vchan_path, 0, 0);
438         if (!state.ctrl) {
439             perror("libxenvchan_server_init");
440             exit(1);
441         }
442     } else {
443         if (strcmp(socket_path, "-") == 0) {
444             state.input_fd = 0;
445             state.output_fd = 1;
446         } else {
447             socket_fd = listen_socket(socket_path);
448             if (socket_fd == -1) {
449                 fprintf(stderr, "listen socket failed\n");
450                 return 1;
451             }
452         }
453     }
454 
455     if (state_path) {
456         struct xs_handle *xs;
457 
458         xs = xs_open(0);
459         if (!xs) {
460             perror("xs_open");
461             return 1;
462         }
463         if (!xs_write(xs, XBT_NULL, state_path, "running", strlen("running"))) {
464             perror("xs_write");
465             return 1;
466         }
467         xs_close(xs);
468     }
469 
470     ret = 0;
471 
472     for (;;) {
473         if (is_server) {
474             /* wait for vchan connection */
475             while (libxenvchan_is_open(state.ctrl) != 1)
476                 libxenvchan_wait(state.ctrl);
477             /* vchan client connected, setup local FD if needed */
478             if (strcmp(socket_path, "-") == 0) {
479                 state.input_fd = 0;
480                 state.output_fd = 1;
481             } else {
482                 state.input_fd = state.output_fd = connect_socket(socket_path);
483             }
484             if (state.input_fd == -1) {
485                 fprintf(stderr, "connect_socket failed\n");
486                 ret = 1;
487                 break;
488             }
489             if (data_loop(&state) != 0)
490                 break;
491             /* keep it running only when get UNIX socket path */
492             if (socket_path[0] != '/')
493                 break;
494         } else {
495             /* wait for local socket connection */
496             if (strcmp(socket_path, "-") != 0)
497                 state.input_fd = state.output_fd = accept(socket_fd,
498                                                           NULL, NULL);
499             if (state.input_fd == -1) {
500                 perror("accept");
501                 ret = 1;
502                 break;
503             }
504             set_nonblocking(state.input_fd, 1);
505             set_nonblocking(state.output_fd, 1);
506             state.ctrl = connect_vchan(domid, vchan_path);
507             if (!state.ctrl) {
508                 perror("vchan client init");
509                 ret = 1;
510                 break;
511             }
512             if (data_loop(&state) != 0)
513                 break;
514             /* don't reconnect if output was stdout */
515             if (strcmp(socket_path, "-") == 0)
516                 break;
517 
518             libxenvchan_close(state.ctrl);
519             state.ctrl = NULL;
520         }
521     }
522 
523     if (state.output_fd >= 0)
524         close(state.output_fd);
525     if (state.input_fd >= 0)
526         close(state.input_fd);
527     if (state.ctrl)
528         libxenvchan_close(state.ctrl);
529     if (socket_fd >= 0)
530         close(socket_fd);
531 
532     return ret;
533 }
534