mirror of https://github.com/xemu-project/xemu.git
telnet protocol and more consistent syntax (Jason Wessel)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2030 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
parent
1f6e24e73c
commit
951f13516a
|
@ -525,43 +525,60 @@ Write output to filename. No character can be read.
|
||||||
name pipe @var{filename}
|
name pipe @var{filename}
|
||||||
@item COMn
|
@item COMn
|
||||||
[Windows only] Use host serial port @var{n}
|
[Windows only] Use host serial port @var{n}
|
||||||
@item udp:remote_port
|
@item udp:[remote_host]:remote_port[@@[src_ip]:src_port]
|
||||||
UDP Net Console sent to locahost at remote_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.
|
||||||
@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
|
If you just want a simple readonly console you can use @code{netcat} or
|
||||||
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}, 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
|
||||||
@code{nc -u -l -p 4555}. Any time qemu writes something to that port
|
will appear in the netconsole session.
|
||||||
it will appear in the netconsole session.
|
|
||||||
|
|
||||||
If you plan to send characters back via netconsole or you want to stop
|
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
|
and start qemu a lot of times, you should have qemu use the same
|
||||||
source port each time by using something like @code{-serial
|
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
|
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
|
characters via udp. If you have a patched version of netcat which
|
||||||
activates telnet remote echo and single char transfer, then you can
|
activates telnet remote echo and single char transfer, then you can
|
||||||
use the following options to step up a netcat redirector to allow
|
use the following options to step up a netcat redirector to allow
|
||||||
telnet on port 5555 to access the qemu port.
|
telnet on port 5555 to access the qemu port.
|
||||||
@table @code
|
@table @code
|
||||||
@item Qemu Options
|
@item Qemu Options:
|
||||||
-serial udp:4556:localhost:4555
|
-serial udp::4555@@:4556
|
||||||
@item netcat options
|
@item netcat options:
|
||||||
-u -P 4555 -L localhost:4556 -t -p 5555 -I -T
|
-u -P 4555 -L 0.0.0.0:4556 -t -p 5555 -I -T
|
||||||
|
@item telnet options:
|
||||||
|
localhost 5555
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
|
|
||||||
@item tcp:remote_host:remote_port
|
@item tcp:[host]:port[,server][,nowait]
|
||||||
TCP Net Console sent to remote_host at the remote_port
|
The TCP Net Console has two modes of operation. It can send the serial
|
||||||
@item tcpl:host:port
|
I/O to a location or wait for a connection from a location. By default
|
||||||
TCP Net Console: wait for connection on @var{host} on the local port
|
the TCP Net Console is sent to @var{host} at the @var{port}. If you use
|
||||||
@var{port}. If host is omitted, 0.0.0.0 is assumed. Only one TCP
|
the @var{,server} option QEMU will wait for a client socket application
|
||||||
connection at a time is accepted. You can use @code{telnet} to connect
|
to connect to the port before continuing, unless the @code{,nowait}
|
||||||
to the corresponding character device.
|
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.
|
||||||
|
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
@item -parallel dev
|
@item -parallel dev
|
||||||
|
|
208
vl.c
208
vl.c
|
@ -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_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 *qemu_chr_open_udp(const char *def)
|
||||||
{
|
{
|
||||||
CharDriverState *chr = NULL;
|
CharDriverState *chr = NULL;
|
||||||
NetCharDriver *s = NULL;
|
NetCharDriver *s = NULL;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
int con_type;
|
struct sockaddr_in saddr;
|
||||||
struct sockaddr_in addr;
|
|
||||||
const char *p, *r;
|
|
||||||
int port;
|
|
||||||
|
|
||||||
chr = qemu_mallocz(sizeof(CharDriverState));
|
chr = qemu_mallocz(sizeof(CharDriverState));
|
||||||
if (!chr)
|
if (!chr)
|
||||||
|
@ -2227,58 +2227,12 @@ CharDriverState *qemu_chr_open_udp(const char *def)
|
||||||
goto return_err;
|
goto return_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* There are three types of port definitions
|
if (parse_host_src_port(&s->daddr, &saddr, def) < 0) {
|
||||||
* 1) udp:remote_port
|
printf("Could not parse: %s\n", def);
|
||||||
* Juse use 0.0.0.0 for the IP and send to remote
|
goto return_err;
|
||||||
* 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;
|
if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
|
||||||
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 (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
|
||||||
{
|
{
|
||||||
perror("bind");
|
perror("bind");
|
||||||
goto return_err;
|
goto return_err;
|
||||||
|
@ -2312,6 +2266,7 @@ typedef struct {
|
||||||
int fd, listen_fd;
|
int fd, listen_fd;
|
||||||
int connected;
|
int connected;
|
||||||
int max_size;
|
int max_size;
|
||||||
|
int do_telnetopt;
|
||||||
} TCPCharDriver;
|
} TCPCharDriver;
|
||||||
|
|
||||||
static void tcp_chr_accept(void *opaque);
|
static void tcp_chr_accept(void *opaque);
|
||||||
|
@ -2337,6 +2292,56 @@ static int tcp_chr_read_poll(void *opaque)
|
||||||
return s->max_size;
|
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)
|
static void tcp_chr_read(void *opaque)
|
||||||
{
|
{
|
||||||
CharDriverState *chr = opaque;
|
CharDriverState *chr = opaque;
|
||||||
|
@ -2360,7 +2365,10 @@ static void tcp_chr_read(void *opaque)
|
||||||
closesocket(s->fd);
|
closesocket(s->fd);
|
||||||
s->fd = -1;
|
s->fd = -1;
|
||||||
} else if (size > 0) {
|
} 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);
|
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)
|
static void tcp_chr_accept(void *opaque)
|
||||||
{
|
{
|
||||||
CharDriverState *chr = opaque;
|
CharDriverState *chr = opaque;
|
||||||
|
@ -2399,6 +2422,8 @@ static void tcp_chr_accept(void *opaque)
|
||||||
if (fd < 0 && errno != EINTR) {
|
if (fd < 0 && errno != EINTR) {
|
||||||
return;
|
return;
|
||||||
} else if (fd >= 0) {
|
} else if (fd >= 0) {
|
||||||
|
if (s->do_telnetopt)
|
||||||
|
tcp_chr_telnet_init(fd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2419,16 +2444,34 @@ static void tcp_chr_close(CharDriverState *chr)
|
||||||
}
|
}
|
||||||
|
|
||||||
static CharDriverState *qemu_chr_open_tcp(const char *host_str,
|
static CharDriverState *qemu_chr_open_tcp(const char *host_str,
|
||||||
int is_listen)
|
int is_telnet)
|
||||||
{
|
{
|
||||||
CharDriverState *chr = NULL;
|
CharDriverState *chr = NULL;
|
||||||
TCPCharDriver *s = NULL;
|
TCPCharDriver *s = NULL;
|
||||||
int fd = -1, ret, err, val;
|
int fd = -1, ret, err, val;
|
||||||
|
int is_listen = 0;
|
||||||
|
int is_waitconnect = 1;
|
||||||
|
const char *ptr;
|
||||||
struct sockaddr_in saddr;
|
struct sockaddr_in saddr;
|
||||||
|
|
||||||
if (parse_host_port(&saddr, host_str) < 0)
|
if (parse_host_port(&saddr, host_str) < 0)
|
||||||
goto fail;
|
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));
|
chr = qemu_mallocz(sizeof(CharDriverState));
|
||||||
if (!chr)
|
if (!chr)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -2439,7 +2482,9 @@ static CharDriverState *qemu_chr_open_tcp(const char *host_str,
|
||||||
fd = socket(PF_INET, SOCK_STREAM, 0);
|
fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
socket_set_nonblock(fd);
|
|
||||||
|
if (!is_waitconnect)
|
||||||
|
socket_set_nonblock(fd);
|
||||||
|
|
||||||
s->connected = 0;
|
s->connected = 0;
|
||||||
s->fd = -1;
|
s->fd = -1;
|
||||||
|
@ -2457,6 +2502,8 @@ static CharDriverState *qemu_chr_open_tcp(const char *host_str,
|
||||||
goto fail;
|
goto fail;
|
||||||
s->listen_fd = fd;
|
s->listen_fd = fd;
|
||||||
qemu_set_fd_handler(s->listen_fd, tcp_chr_accept, NULL, chr);
|
qemu_set_fd_handler(s->listen_fd, tcp_chr_accept, NULL, chr);
|
||||||
|
if (is_telnet)
|
||||||
|
s->do_telnetopt = 1;
|
||||||
} else {
|
} else {
|
||||||
for(;;) {
|
for(;;) {
|
||||||
ret = connect(fd, (struct sockaddr *)&saddr, sizeof(saddr));
|
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_write = tcp_chr_write;
|
||||||
chr->chr_add_read_handler = tcp_chr_add_read_handler;
|
chr->chr_add_read_handler = tcp_chr_add_read_handler;
|
||||||
chr->chr_close = tcp_chr_close;
|
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;
|
return chr;
|
||||||
fail:
|
fail:
|
||||||
if (fd >= 0)
|
if (fd >= 0)
|
||||||
|
@ -2505,7 +2558,7 @@ CharDriverState *qemu_chr_open(const char *filename)
|
||||||
if (strstart(filename, "tcp:", &p)) {
|
if (strstart(filename, "tcp:", &p)) {
|
||||||
return qemu_chr_open_tcp(p, 0);
|
return qemu_chr_open_tcp(p, 0);
|
||||||
} else
|
} else
|
||||||
if (strstart(filename, "tcpl:", &p)) {
|
if (strstart(filename, "telnet:", &p)) {
|
||||||
return qemu_chr_open_tcp(p, 1);
|
return qemu_chr_open_tcp(p, 1);
|
||||||
} else
|
} else
|
||||||
if (strstart(filename, "udp:", &p)) {
|
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;
|
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)
|
int parse_host_port(struct sockaddr_in *saddr, const char *str)
|
||||||
{
|
{
|
||||||
char buf[512];
|
char buf[512];
|
||||||
|
|
Loading…
Reference in New Issue