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