Commit 951f1351 authored by bellard's avatar bellard
Browse files

telnet protocol and more consistent syntax (Jason Wessel)


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2030 c046a42c-6fe2-441c-8c8c-71466251a162
parent 1f6e24e7
......@@ -525,43 +525,60 @@ Write output to filename. No character can be read.
name pipe @var{filename}
@item COMn
[Windows only] Use host serial port @var{n}
@item udp:remote_port
UDP Net Console sent to locahost at remote_port
@item udp:remote_host:remote_port
UDP Net Console sent to remote_host at remote_port
@item udp:src_port:remote_host:remote_port
UDP Net Console sent from src_port to remote_host at the remote_port.
The udp:* sub options are primary intended for netconsole. If you
just want a simple readonly console you can use @code{netcat} or
@code{nc}, by starting qemu with: @code{-serial udp:4555} and nc as:
@code{nc -u -l -p 4555}. Any time qemu writes something to that port
it will appear in the netconsole session.
@item udp:[remote_host]:remote_port[@@[src_ip]:src_port]
This implements UDP Net Console. When @var{remote_host} or @var{src_ip} are not specified they default to @code{0.0.0.0}. When not using a specifed @var{src_port} a random port is automatically chosen.
If you just want a simple readonly console you can use @code{netcat} or
@code{nc}, by starting qemu with: @code{-serial udp::4555} and nc as:
@code{nc -u -l -p 4555}. Any time qemu writes something to that port it
will appear in the netconsole session.
If you plan to send characters back via netconsole or you want to stop
and start qemu a lot of times, you should have qemu use the same
source port each time by using something like @code{-serial
udp:4556:localhost:4555} to qemu. Another approach is to use a patched
udp::4555@@:4556} to qemu. Another approach is to use a patched
version of netcat which can listen to a TCP port and send and receive
characters via udp. If you have a patched version of netcat which
activates telnet remote echo and single char transfer, then you can
use the following options to step up a netcat redirector to allow
telnet on port 5555 to access the qemu port.
@table @code
@item Qemu Options
-serial udp:4556:localhost:4555
@item netcat options
-u -P 4555 -L localhost:4556 -t -p 5555 -I -T
@item Qemu Options:
-serial udp::4555@@:4556
@item netcat options:
-u -P 4555 -L 0.0.0.0:4556 -t -p 5555 -I -T
@item telnet options:
localhost 5555
@end table
@item tcp:[host]:port[,server][,nowait]
The TCP Net Console has two modes of operation. It can send the serial
I/O to a location or wait for a connection from a location. By default
the TCP Net Console is sent to @var{host} at the @var{port}. If you use
the @var{,server} option QEMU will wait for a client socket application
to connect to the port before continuing, unless the @code{,nowait}
option was specified. If @var{host} is omitted, 0.0.0.0 is assumed. Only
one TCP connection at a time is accepted. You can use @code{telnet} to
connect to the corresponding character device.
@table @code
@item Example to send tcp console to 192.168.0.2 port 4444
-serial tcp:192.168.0.2:4444
@item Example to listen and wait on port 4444 for connection
-serial tcp::4444,server
@item Example to not wait and listen on ip 192.168.0.100 port 4444
-serial tcp:192.168.0.100:4444,server,nowait
@end table
@item telnet:host:port[,server][,nowait]
The telnet protocol is used instead of raw tcp sockets. The options
work the same as if you had specified @code{-serial tcp}. The
difference is that the port acts like a telnet server or client using
telnet option negotiation. This will also allow you to send the
MAGIC_SYSRQ sequence if you use a telnet that supports sending the break
sequence. Typically in unix telnet you do it with Control-] and then
type "send break" followed by pressing the enter key.
@item tcp:remote_host:remote_port
TCP Net Console sent to remote_host at the remote_port
@item tcpl:host:port
TCP Net Console: wait for connection on @var{host} on the local port
@var{port}. If host is omitted, 0.0.0.0 is assumed. Only one TCP
connection at a time is accepted. You can use @code{telnet} to connect
to the corresponding character device.
@end table
@item -parallel dev
......
......@@ -2203,16 +2203,16 @@ static void udp_chr_add_read_handler(CharDriverState *chr,
}
int parse_host_port(struct sockaddr_in *saddr, const char *str);
int parse_host_src_port(struct sockaddr_in *haddr,
struct sockaddr_in *saddr,
const char *str);
CharDriverState *qemu_chr_open_udp(const char *def)
{
CharDriverState *chr = NULL;
NetCharDriver *s = NULL;
int fd = -1;
int con_type;
struct sockaddr_in addr;
const char *p, *r;
int port;
struct sockaddr_in saddr;
chr = qemu_mallocz(sizeof(CharDriverState));
if (!chr)
......@@ -2227,58 +2227,12 @@ CharDriverState *qemu_chr_open_udp(const char *def)
goto return_err;
}
/* There are three types of port definitions
* 1) udp:remote_port
* Juse use 0.0.0.0 for the IP and send to remote
* 2) udp:remote_host:port
* Use a IP and send traffic to remote
* 3) udp:local_port:remote_host:remote_port
* Use local_port as the originator + #2
*/
con_type = 0;
p = def;
while ((p = strchr(p, ':'))) {
p++;
con_type++;
}
p = def;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
s->daddr.sin_family = AF_INET;
s->daddr.sin_addr.s_addr = htonl(INADDR_ANY);
switch (con_type) {
case 0:
port = strtol(p, (char **)&r, 0);
if (r == p) {
fprintf(stderr, "Error parsing port number\n");
goto return_err;
}
s->daddr.sin_port = htons((short)port);
break;
case 2:
port = strtol(p, (char **)&r, 0);
if (r == p) {
fprintf(stderr, "Error parsing port number\n");
goto return_err;
}
addr.sin_port = htons((short)port);
p = r + 1;
/* Fall through to case 1 now that we have the local port */
case 1:
if (parse_host_port(&s->daddr, p) < 0) {
fprintf(stderr, "Error parsing host name and port\n");
goto return_err;
}
break;
default:
fprintf(stderr, "Too many ':' characters\n");
goto return_err;
if (parse_host_src_port(&s->daddr, &saddr, def) < 0) {
printf("Could not parse: %s\n", def);
goto return_err;
}
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind");
goto return_err;
......@@ -2312,6 +2266,7 @@ typedef struct {
int fd, listen_fd;
int connected;
int max_size;
int do_telnetopt;
} TCPCharDriver;
static void tcp_chr_accept(void *opaque);
......@@ -2337,6 +2292,56 @@ static int tcp_chr_read_poll(void *opaque)
return s->max_size;
}
#define IAC 255
#define IAC_BREAK 243
static void tcp_chr_process_IAC_bytes(CharDriverState *chr,
TCPCharDriver *s,
char *buf, int *size)
{
/* Handle any telnet client's basic IAC options to satisfy char by
* char mode with no echo. All IAC options will be removed from
* the buf and the do_telnetopt variable will be used to track the
* state of the width of the IAC information.
*
* IAC commands come in sets of 3 bytes with the exception of the
* "IAC BREAK" command and the double IAC.
*/
int i;
int j = 0;
for (i = 0; i < *size; i++) {
if (s->do_telnetopt > 1) {
if ((unsigned char)buf[i] == IAC && s->do_telnetopt == 2) {
/* Double IAC means send an IAC */
if (j != i)
buf[j] = buf[i];
j++;
s->do_telnetopt = 1;
} else {
if ((unsigned char)buf[i] == IAC_BREAK && s->do_telnetopt == 2) {
/* Handle IAC break commands by sending a serial break */
chr->chr_event(s->fd_opaque, CHR_EVENT_BREAK);
s->do_telnetopt++;
}
s->do_telnetopt++;
}
if (s->do_telnetopt >= 4) {
s->do_telnetopt = 1;
}
} else {
if ((unsigned char)buf[i] == IAC) {
s->do_telnetopt = 2;
} else {
if (j != i)
buf[j] = buf[i];
j++;
}
}
}
*size = j;
}
static void tcp_chr_read(void *opaque)
{
CharDriverState *chr = opaque;
......@@ -2360,7 +2365,10 @@ static void tcp_chr_read(void *opaque)
closesocket(s->fd);
s->fd = -1;
} else if (size > 0) {
s->fd_read(s->fd_opaque, buf, size);
if (s->do_telnetopt)
tcp_chr_process_IAC_bytes(chr, s, buf, &size);
if (size > 0)
s->fd_read(s->fd_opaque, buf, size);
}
}
......@@ -2385,6 +2393,21 @@ static void tcp_chr_connect(void *opaque)
tcp_chr_read, NULL, chr);
}
#define IACSET(x,a,b,c) x[0] = a; x[1] = b; x[2] = c;
static void tcp_chr_telnet_init(int fd)
{
char buf[3];
/* Send the telnet negotion to put telnet in binary, no echo, single char mode */
IACSET(buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */
send(fd, (char *)buf, 3, 0);
IACSET(buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead */
send(fd, (char *)buf, 3, 0);
IACSET(buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */
send(fd, (char *)buf, 3, 0);
IACSET(buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */
send(fd, (char *)buf, 3, 0);
}
static void tcp_chr_accept(void *opaque)
{
CharDriverState *chr = opaque;
......@@ -2399,6 +2422,8 @@ static void tcp_chr_accept(void *opaque)
if (fd < 0 && errno != EINTR) {
return;
} else if (fd >= 0) {
if (s->do_telnetopt)
tcp_chr_telnet_init(fd);
break;
}
}
......@@ -2419,16 +2444,34 @@ static void tcp_chr_close(CharDriverState *chr)
}
static CharDriverState *qemu_chr_open_tcp(const char *host_str,
int is_listen)
int is_telnet)
{
CharDriverState *chr = NULL;
TCPCharDriver *s = NULL;
int fd = -1, ret, err, val;
int is_listen = 0;
int is_waitconnect = 1;
const char *ptr;
struct sockaddr_in saddr;
if (parse_host_port(&saddr, host_str) < 0)
goto fail;
ptr = host_str;
while((ptr = strchr(ptr,','))) {
ptr++;
if (!strncmp(ptr,"server",6)) {
is_listen = 1;
} else if (!strncmp(ptr,"nowait",6)) {
is_waitconnect = 0;
} else {
printf("Unknown option: %s\n", ptr);
goto fail;
}
}
if (!is_listen)
is_waitconnect = 0;
chr = qemu_mallocz(sizeof(CharDriverState));
if (!chr)
goto fail;
......@@ -2439,7 +2482,9 @@ static CharDriverState *qemu_chr_open_tcp(const char *host_str,
fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd < 0)
goto fail;
socket_set_nonblock(fd);
if (!is_waitconnect)
socket_set_nonblock(fd);
s->connected = 0;
s->fd = -1;
......@@ -2457,6 +2502,8 @@ static CharDriverState *qemu_chr_open_tcp(const char *host_str,
goto fail;
s->listen_fd = fd;
qemu_set_fd_handler(s->listen_fd, tcp_chr_accept, NULL, chr);
if (is_telnet)
s->do_telnetopt = 1;
} else {
for(;;) {
ret = connect(fd, (struct sockaddr *)&saddr, sizeof(saddr));
......@@ -2484,6 +2531,12 @@ static CharDriverState *qemu_chr_open_tcp(const char *host_str,
chr->chr_write = tcp_chr_write;
chr->chr_add_read_handler = tcp_chr_add_read_handler;
chr->chr_close = tcp_chr_close;
if (is_listen && is_waitconnect) {
printf("QEMU waiting for connection on: %s\n", host_str);
tcp_chr_accept(chr);
socket_set_nonblock(s->listen_fd);
}
return chr;
fail:
if (fd >= 0)
......@@ -2505,7 +2558,7 @@ CharDriverState *qemu_chr_open(const char *filename)
if (strstart(filename, "tcp:", &p)) {
return qemu_chr_open_tcp(p, 0);
} else
if (strstart(filename, "tcpl:", &p)) {
if (strstart(filename, "telnet:", &p)) {
return qemu_chr_open_tcp(p, 1);
} else
if (strstart(filename, "udp:", &p)) {
......@@ -2618,6 +2671,45 @@ static int get_str_sep(char *buf, int buf_size, const char **pp, int sep)
return 0;
}
int parse_host_src_port(struct sockaddr_in *haddr,
struct sockaddr_in *saddr,
const char *input_str)
{
char *str = strdup(input_str);
char *host_str = str;
char *src_str;
char *ptr;
/*
* Chop off any extra arguments at the end of the string which
* would start with a comma, then fill in the src port information
* if it was provided else use the "any address" and "any port".
*/
if ((ptr = strchr(str,',')))
*ptr = '\0';
if ((src_str = strchr(input_str,'@'))) {
*src_str = '\0';
src_str++;
}
if (parse_host_port(haddr, host_str) < 0)
goto fail;
if (!src_str || *src_str == '\0')
src_str = ":0";
if (parse_host_port(saddr, src_str) < 0)
goto fail;
free(str);
return(0);
fail:
free(str);
return -1;
}
int parse_host_port(struct sockaddr_in *saddr, const char *str)
{
char buf[512];
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment