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@xxxxxxxxxx>
+ * Copyright (C) International Business Machines Corp., 2005, 2006
+ * Author: Anthony Liguori <aliguori@xxxxxxxxxx>
+ * Author: Muli Ben-Yehuda <muli@xxxxxxxxxx>
*
- * 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@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-devel
|