Muli Ben-Yehuda
2006-Sep-06 07:53 UTC
[Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
The attached patch makes xenconsole send and receive console messages over a remote connection, instead of via stdin/stdout - if given the --remote switch. It is useful for proxy''ing real console message or other protocol messages (such as gdb) between dom0 and a remote host. We''re currently using it for proxying gdb between gdbstub in a partition that talks gdb over the console page to a remote host running gdb. Is this something that would be interesting for inclusion? if yes, I''ll be happy to split it into smaller, more-digestable chunks. To use: In dom0: xenconsole --remote --gateway --port $PORT $DOMID In remote host: telnet $DOM0IP $PORT, or # nc $DOM0IP $PORT, or gdb''s target remote command Cheers, Muli diff -r ec03b24a2d83 -r 03ce605e7542 tools/console/client/main.c --- a/tools/console/client/main.c Tue Aug 15 19:53:55 2006 +0100 +++ b/tools/console/client/main.c Sun Sep 03 15:13:01 2006 +0300 @@ -1,8 +1,9 @@ /*\ - * Copyright (C) International Business Machines Corp., 2005 - * Author(s): Anthony Liguori <aliguori@us.ibm.com> + * Copyright (C) International Business Machines Corp., 2005, 2006 + * Author: Anthony Liguori <aliguori@us.ibm.com> + * Author: Muli Ben-Yehuda <muli@il.ibm.com> * - * Xen Console Daemon + * Xen Console Client * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,32 +36,57 @@ #include <err.h> #include <errno.h> #include <pty.h> +#include <netinet/in.h> +#include <netdb.h> +#include <stdarg.h> #include "xs.h" #define ESCAPE_CHARACTER 0x1d +#define DEFAULT_LISTEN_PORT 7890 +#define MSG_SIZE 512 + +struct remote { + long port; + int do_listen; + int gateway; + int noecho; + int server; /* server socket */ +}; + +struct message { + struct message *next; + char* data; + size_t len; +}; + +struct queue { + struct message *head; + struct message *tail; +}; static volatile sig_atomic_t received_signal = 0; +static int debug; + +#define dbg(fmt, args...) do { \ + if (debug) \ + _dbg("[%s] " fmt, __func__, ##args); \ +} while (0) + +static int _dbg(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fflush(stderr); +} + static void sighandler(int signum) { received_signal = 1; -} - -static bool write_sync(int fd, const void *data, size_t size) -{ - size_t offset = 0; - ssize_t len; - - while (offset < size) { - len = write(fd, data + offset, size - offset); - if (len < 1) { - return false; - } - offset += len; - } - - return true; } static void usage(const char *program) { @@ -68,7 +94,32 @@ static void usage(const char *program) { "Attaches to a virtual domain console\n" "\n" " -h, --help display this help and exit\n" + " -r, --remote wait for connections from local clients\n" + " -g, --gateway allow connections from any host\n" + " -n, --noecho cancel echo\n" + " -d, --debug enable debug output\n" , program); +} + +ssize_t write_all(int fd, const void *buf, size_t count) +{ + const unsigned char* b = (const unsigned char*)buf; + ssize_t sum = 0; + ssize_t ret = 0; + + while (count) { + ret = write(fd, b, count); + if (ret == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + ret = -errno; + break; + } + count -= ret; + b += ret; + sum += ret; + } + return (ret >= 0 ? sum : ret); } /* don''t worry too much if setting terminal attributes fail */ @@ -91,79 +142,360 @@ static void restore_term(int fd, struct tcsetattr(fd, TCSAFLUSH, old); } -static int console_loop(int fd) -{ - int ret; +static struct message *alloc_msg(char* data, size_t len) +{ + struct message *msg; + + msg = malloc(sizeof(*msg)); + if (!msg) + return NULL; + + memset(msg, 0, sizeof(*msg)); + + msg->next = NULL; + msg->data = data; + msg->len = len; + + return msg; +} + +static void destroy_msg(struct message *msg) +{ + msg->len = -1; + free(msg->data); + msg->data = (char*)0xBADF00D1; + free(msg); +} + +static void __queue_msg(struct queue *q, struct message *msg) +{ + if (q->tail) + q->tail->next = msg; + else + q->head = q->tail = msg; +} + +static int queue_msg(struct queue *q, char *data, size_t len) +{ + struct message *msg; + + msg = alloc_msg(data, len); + if (!msg) + return -ENOMEM; + + __queue_msg(q, msg); + + return 0; +} + +static int dequeue_msg(struct queue *q, struct message **pmsg) +{ + struct message *tmp; + + tmp = q->head; + if (!tmp) + return 0; /* nothing to do */ + + q->head = tmp->next; + + if (q->tail == tmp) + q->tail = tmp->next; + + *pmsg = tmp; + return 1; +} + +static int same_msg(const char *m1, size_t len1, const struct message *m2) +{ + int ret; + + if (len1 != m2->len) + return 0; + + ret = !memcmp(m1, m2->data, len1); + + return ret; +} + +static inline void dump_msg(char* prefix, int fd, const char *data, size_t len) +{ + dbg("%s: %u bytes on %d:\n", prefix, len, fd); + dbg("msg: `%s''\n", data); +} + +static int handle_read_fd(int fd, struct queue* q, struct queue* discard) +{ + int ret; + ssize_t len; + char *msg; + struct message *msg_to_discard; + + msg = malloc(MSG_SIZE); + if (!msg) + return -ENOMEM; + + len = read(fd, msg, MSG_SIZE); + if (len == 1 && msg[0] == ESCAPE_CHARACTER) { + ret = ECONNRESET; + goto free_msg; + } + + if (len == -1) { + if (errno == EINTR || errno == EAGAIN) + ret = EINTR; + else + ret = -errno; + goto free_msg; + } + if (len == 0) { + /* try to reconnect */ + ret = EAGAIN; + goto free_msg; + } + + dump_msg("read", fd, msg, len); + + msg_to_discard = NULL; + if (discard) + ret = dequeue_msg(discard, &msg_to_discard); + + if (!msg_to_discard || !same_msg(msg, len, msg_to_discard)) { + dbg("queing %p on queue %p\n", msg, q); + ret = queue_msg(q, msg, len); + if (msg_to_discard) + destroy_msg(msg_to_discard); + goto done; + } else { /* discard it */ + dbg("discarding %p\n", msg); + destroy_msg(msg_to_discard); + ret = 0; + goto free_msg; + } + + free_msg: + free(msg); + done: + return ret; +} + +static int handle_write_fd(int fd, struct queue* q, struct queue *discard) +{ + int ret; + struct message *pmsg; do { - fd_set fds; - - FD_ZERO(&fds); - FD_SET(STDIN_FILENO, &fds); - FD_SET(fd, &fds); - - ret = select(fd + 1, &fds, NULL, NULL, NULL); + ret = dequeue_msg(q, &pmsg); + + if (ret < 0) /* error */ + goto done; + + /* no more messages */ + if (ret == 0) + goto done; + + dump_msg("write", fd, pmsg->data, pmsg->len); + + ret = write_all(fd, pmsg->data, pmsg->len); + if (ret < 0) + goto free_msg; + + if (discard) { + dbg("discard set, queueing %p for discard check\n", + pmsg->data); + __queue_msg(discard, pmsg); + } else + destroy_msg(pmsg); + } while (1); + + free_msg: + destroy_msg(pmsg); + done: + return ret; +} + +static int console_loop(int conspty, int infd, int outfd, int noecho) +{ + int ret; + int max; + + struct queue console = { + .head = NULL, + .tail = NULL, + }; + + struct queue out = { + .head = NULL, + .tail = NULL, + }; + + struct queue discard_queue = { + .head = NULL, + .tail = NULL, + }; + + struct queue *discard = (noecho ? &discard_queue : NULL); + + do { + fd_set rfds, wfds; + + FD_ZERO(&rfds); + FD_SET(infd, &rfds); + FD_SET(conspty, &rfds); + + FD_ZERO(&wfds); + FD_SET(conspty, &wfds); + FD_SET(outfd, &wfds); + + max = (conspty | infd | outfd) + 1; + + ret = select(max, &rfds, &wfds, NULL, NULL); + if (ret == -1) { if (errno == EINTR || errno == EAGAIN) { continue; } - return -1; - } - - if (FD_ISSET(STDIN_FILENO, &fds)) { - ssize_t len; - char msg[60]; - - len = read(STDIN_FILENO, msg, sizeof(msg)); - if (len == 1 && msg[0] == ESCAPE_CHARACTER) { + return -errno; + } + + if (FD_ISSET(infd, &rfds)) { + ret = handle_read_fd(infd, &console, NULL); + if (ret < 0) + return ret; + if (ret == EINTR) + continue; + if (ret == EAGAIN) + return EAGAIN; + if (ret == ECONNRESET) return 0; - } - - if (len == 0 || len == -1) { - if (len == -1 && - (errno == EINTR || errno == EAGAIN)) { - continue; - } - return -1; - } - - if (!write_sync(fd, msg, len)) { - perror("write() failed"); - return -1; - } - } - - if (FD_ISSET(fd, &fds)) { - ssize_t len; - char msg[512]; - - len = read(fd, msg, sizeof(msg)); - if (len == 0 || len == -1) { - if (len == -1 && - (errno == EINTR || errno == EAGAIN)) { - continue; - } - return -1; - } - - if (!write_sync(STDOUT_FILENO, msg, len)) { - perror("write() failed"); - return -1; - } + } + + if (FD_ISSET(conspty, &rfds)) { + ret = handle_read_fd(conspty, &out, discard); + if (ret < 0) + return ret; + if (ret == EINTR) + continue; + if (ret == EAGAIN) + return EAGAIN; + if (ret == ECONNRESET) + return 0; + } + + if (FD_ISSET(outfd, &wfds)) { + ret = handle_write_fd(outfd, &out, NULL); + if (ret < 0) + return ret; + } + + if (FD_ISSET(conspty, &wfds)) { + ret = handle_write_fd(conspty, &console, discard); + if (ret < 0) + return ret; } } while (received_signal == 0); return 0; } +static int start_server(struct remote *remote) +{ + struct sockaddr_in sa; + struct hostent *he = NULL; + int ret; + int on; + + memset(&sa, 0, sizeof(sa)); + + sa.sin_family = AF_INET; + sa.sin_port = htons(remote->port); + + if (!remote->gateway) { + he = gethostbyname("localhost"); + if (!he) + err(h_errno, "could not get localhost address\n"); + memcpy(&sa.sin_addr, he->h_addr_list[0], he->h_length); + } else + sa.sin_addr.s_addr = htonl(INADDR_ANY); + + remote->server = socket(AF_INET, SOCK_STREAM, 0); + if (remote->server < 0) + err(errno, "socket failed\n"); + + on = 1; + ret = setsockopt(remote->server, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (ret) + err(errno, "setsockopt(SO_REUSEADDR) failed\n"); + + ret = bind(remote->server, (struct sockaddr *)&sa, sizeof(sa)); + if (ret) + err(errno, "bind failed\n"); + + ret = listen(remote->server, 5); + if (ret) + err(errno, "listen failed\n"); + + return ret; +} + +static int remote_loop(int conspty, struct remote *remote) +{ + int in; + int out; + int socket; + int ret; + + ret = start_server(remote); + if (ret) + return ret; + + do { + socket = accept(remote->server, NULL, 0); + if (socket < 0) + err(errno, "accept failed\n"); + + in = socket; + out = socket; + + ret = console_loop(conspty, in, out, remote->noecho); + } while (ret == EAGAIN); + + return ret; +} + +static int main_loop(int conspty, struct remote *remote) +{ + int in; + int out; + int ret; + struct termios attr; + + if (remote->do_listen) + return remote_loop(conspty, remote); + + init_term(fileno(stdin), &attr); + + in = fileno(stdin); + out = fileno(stdout); + + ret = console_loop(conspty, in, out, 0); + + restore_term(in, &attr); + + return ret; +} + int main(int argc, char **argv) { - struct termios attr; int domid; - char *sopt = "h"; + char *sopt = "rp:gndh"; int ch; int opt_ind=0; struct option lopt[] = { + { "remote", 0, NULL, ''r'' }, + { "port", 1, NULL, ''p'' }, + { "gateway", 0, NULL, ''g'' }, + { "noecho", 0, NULL, ''n'' }, + { "debug", 0, NULL, ''d'' }, { "help", 0, 0, ''h'' }, { 0 }, @@ -174,12 +506,42 @@ int main(int argc, char **argv) struct xs_handle *xs; char *end; time_t now; + struct remote remote = { + .port = DEFAULT_LISTEN_PORT, + .do_listen = 0, + .gateway = 0, + .noecho = 0, + .server = -1, + }; + int ret; while((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { switch(ch) { case ''h'': usage(argv[0]); exit(0); + break; + case ''r'': + remote.do_listen = 1; + break; + case ''p'': + remote.port = strtol(optarg, &end, 10); + if (end && *end) { + fprintf(stderr, "Invalid port `%s'' specified\n", + optarg); + fprintf(stderr, "Try `%s --help'' for more " + "information.\n", argv[0]); + exit(EINVAL); + } + break; + case ''g'': + remote.gateway = 1; + break; + case ''n'': + remote.noecho = 1; + break; + case ''d'': + debug = 1; break; } } @@ -194,6 +556,14 @@ int main(int argc, char **argv) domid = strtol(argv[optind], &end, 10); if (end && *end) { fprintf(stderr, "Invalid DOMID `%s''\n", argv[optind]); + fprintf(stderr, "Try `%s --help'' for more information.\n", + argv[0]); + exit(EINVAL); + } + + if (remote.gateway && !remote.do_listen) { + fprintf(stderr, "setting `gateway'' requires also setting " + "`remote''\n"); fprintf(stderr, "Try `%s --help'' for more information.\n", argv[0]); exit(EINVAL); @@ -252,9 +622,9 @@ int main(int argc, char **argv) free(str_pty); free(path); - init_term(STDIN_FILENO, &attr); - console_loop(spty); - restore_term(STDIN_FILENO, &attr); - - return 0; - } + ret = main_loop(spty, &remote); + + xs_daemon_close(xs); + + return ret; +} _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Keir Fraser
2006-Sep-06 15:52 UTC
Re: [Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
On 6/9/06 8:53 am, "Muli Ben-Yehuda" <muli@il.ibm.com> wrote:> The attached patch makes xenconsole send and receive console messages > over a remote connection, instead of via stdin/stdout - if given the > --remote switch. It is useful for proxy''ing real console message or > other protocol messages (such as gdb) between dom0 and a remote > host. We''re currently using it for proxying gdb between gdbstub in a > partition that talks gdb over the console page to a remote host > running gdb. > > Is this something that would be interesting for inclusion? if yes, > I''ll be happy to split it into smaller, more-digestable chunks.Possibly, yes, but this patch seems to do a fair bit more than just add minimal socket support. What''s all the message queue stuff about? Can''t you treat the console I/O as character streams? -- Keir _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Muli Ben-Yehuda
2006-Sep-06 17:22 UTC
Re: [Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
On Wed, Sep 06, 2006 at 04:52:38PM +0100, Keir Fraser wrote:> Possibly, yes, but this patch seems to do a fair bit more than just add > minimal socket support. What''s all the message queue stuff about? Can''t you > treat the console I/O as character streams?A message is just a bunch of characters we''ve read at once. Messages are read from one stream and written to another. After being read and before being written, they''re held on queues. The messages and queues serve two purposes: - they give us a place to hold characters we read from one stream (console pty or socket) until the other becomes writable. With stdout/stdin everything is always readable and writable - with a socket this isn''t necessarily true. - ''noecho'' mode, where we discard stuff that we read back from a given fd after we''ve just written it there ourselves. Without this you get every console command echo''d back to you from the pty immediately. For what it''s worth this happens without the socket support as well, except there we put stdout into raw mode so that the terminal does the ''echo cancelation''. I didn''t find a way to have the pty not echo everything back at me and I didn''t want to rely on the remote tty being in ''raw'' mode, so I implemented it myself. Cheers, Muli _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Keir Fraser
2006-Sep-06 18:01 UTC
Re: [Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
On 6/9/06 18:22, "Muli Ben-Yehuda" <muli@il.ibm.com> wrote:> - ''noecho'' mode, where we discard stuff that we read back from a given > fd after we''ve just written it there ourselves. Without this you get > every console command echo''d back to you from the pty > immediately. For what it''s worth this happens without the socket > support as well, except there we put stdout into raw mode so that > the terminal does the ''echo cancelation''. I didn''t find a way to > have the pty not echo everything back at me and I didn''t want to > rely on the remote tty being in ''raw'' mode, so I implemented it > myself.Why is this a problem? In cases where an echo is really not wanted, like in a gdb control session, the remote end would not be echoing in the first place. -- Keir _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Muli Ben-Yehuda
2006-Sep-06 18:12 UTC
Re: [Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
On Wed, Sep 06, 2006 at 07:01:25PM +0100, Keir Fraser wrote:> On 6/9/06 18:22, "Muli Ben-Yehuda" <muli@il.ibm.com> wrote: > > > - ''noecho'' mode, where we discard stuff that we read back from a given > > fd after we''ve just written it there ourselves. Without this you get > > every console command echo''d back to you from the pty > > immediately. For what it''s worth this happens without the socket > > support as well, except there we put stdout into raw mode so that > > the terminal does the ''echo cancelation''. I didn''t find a way to > > have the pty not echo everything back at me and I didn''t want to > > rely on the remote tty being in ''raw'' mode, so I implemented it > > myself. > > Why is this a problem? In cases where an echo is really not wanted, like in > a gdb control session, the remote end would not be echoing in the first > place.Hmm, are you saying it''s not the pty itself that echo''s stuff back at us, it''s whatever is on the other side? (the console daemon or the domain?). In any case, I have to admit it hasn''t bitten us in practice when using gdb; either gdb is smart enough to handle this on its own or I''m missing something. However, supporting --noecho is still nicer from an aesthetic point of view: compare a console session with and without --noecho and using telnet on the remote side - without it you get everything you write echo''d back at you. Cheers, Muli _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Keir Fraser
2006-Sep-06 18:34 UTC
Re: [Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
On 6/9/06 19:12, "Muli Ben-Yehuda" <muli@il.ibm.com> wrote:>> Why is this a problem? In cases where an echo is really not wanted, like in >> a gdb control session, the remote end would not be echoing in the first >> place. > > Hmm, are you saying it''s not the pty itself that echo''s stuff back at > us, it''s whatever is on the other side? (the console daemon or the > domain?).The echoing comes from the domU itself, unless it''s running some kind of raw-mode client on the console. A normal getty setup will have echoing.> In any case, I have to admit it hasn''t bitten us in practice when > using gdb; either gdb is smart enough to handle this on its own or I''m > missing something. However, supporting --noecho is still nicer from an > aesthetic point of view: compare a console session with and without > --noecho and using telnet on the remote side - without it you get > everything you write echo''d back at you.You mean telnet connected to xenconsole connected to domU? You should run the telnet in raw mode. Problem solved. -- Keir _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Muli Ben-Yehuda
2006-Sep-07 06:51 UTC
Re: [Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
On Wed, Sep 06, 2006 at 07:34:57PM +0100, Keir Fraser wrote:> The echoing comes from the domU itself, unless it''s running some kind of > raw-mode client on the console. A normal getty setup will have > echoing.Ah, I see...> > In any case, I have to admit it hasn''t bitten us in practice when > > using gdb; either gdb is smart enough to handle this on its own or I''m > > missing something. However, supporting --noecho is still nicer from an > > aesthetic point of view: compare a console session with and without > > --noecho and using telnet on the remote side - without it you get > > everything you write echo''d back at you. > > You mean telnet connected to xenconsole connected to domU? You should run > the telnet in raw mode. Problem solved.I assume you mean run the terminal the telnet is running in in raw mode, via ''stty raw''? if yes, it doesn''t work, neither with telnet nor with nc. In any case, since we don''t need it for gdb, I''ll split ''noecho'' out and resubmit. Thanks for the comments! Cheers, Muli _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel
Keir Fraser
2006-Sep-07 12:38 UTC
Re: [Xen-devel] [RFC PATCH] allow connecting to xenconsole from remote hosts
On 7/9/06 07:51, "Muli Ben-Yehuda" <muli@il.ibm.com> wrote:> I assume you mean run the terminal the telnet is running in in raw > mode, via ''stty raw''? if yes, it doesn''t work, neither with telnet nor > with nc. In any case, since we don''t need it for gdb, I''ll split > ''noecho'' out and resubmit. Thanks for the comments!Telnet has a character mode. You enable it by escaping to the control console from within telnet (''^]'') and then typing ''mode char''. Input is then sent immediately to the remote host rather than being batched into lines, and it is not locally echoed. Or use the miniterm program included in the Xen tree''s tools directory. -- Keir _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel