mirror of https://github.com/xqemu/xqemu.git
UDP char device (initial patch by Jason Wessel) - TCP char device
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2007 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
parent
3532fa7402
commit
0bab00f30f
|
@ -496,8 +496,14 @@ Debug/Expert options:
|
||||||
@table @option
|
@table @option
|
||||||
|
|
||||||
@item -serial dev
|
@item -serial dev
|
||||||
Redirect the virtual serial port to host device @var{dev}. Available
|
Redirect the virtual serial port to host character device
|
||||||
devices are:
|
@var{dev}. The default device is @code{vc} in graphical mode and
|
||||||
|
@code{stdio} in non graphical mode.
|
||||||
|
|
||||||
|
This option can be used several times to simulate up to 4 serials
|
||||||
|
ports.
|
||||||
|
|
||||||
|
Available character devices are:
|
||||||
@table @code
|
@table @code
|
||||||
@item vc
|
@item vc
|
||||||
Virtual console
|
Virtual console
|
||||||
|
@ -516,13 +522,47 @@ Write output to filename. No character can be read.
|
||||||
@item stdio
|
@item stdio
|
||||||
[Unix only] standard input/output
|
[Unix only] standard input/output
|
||||||
@item pipe:filename
|
@item pipe:filename
|
||||||
[Unix only] name pipe @var{filename}
|
name pipe @var{filename}
|
||||||
@end table
|
@item COMn
|
||||||
The default device is @code{vc} in graphical mode and @code{stdio} in
|
[Windows only] Use host serial port @var{n}
|
||||||
non graphical mode.
|
@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.
|
||||||
|
|
||||||
This option can be used several times to simulate up to 4 serials
|
The udp:* sub options are primary intended for netconsole. If you
|
||||||
ports.
|
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
|
||||||
|
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
|
||||||
|
@end table
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
@item -parallel dev
|
||||||
Redirect the virtual parallel port to host device @var{dev} (same
|
Redirect the virtual parallel port to host device @var{dev} (same
|
||||||
|
|
381
vl.c
381
vl.c
|
@ -2130,6 +2130,373 @@ CharDriverState *qemu_chr_open_win_file_out(const char *file_out)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/***********************************************************/
|
||||||
|
/* UDP Net console */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
IOCanRWHandler *fd_can_read;
|
||||||
|
IOReadHandler *fd_read;
|
||||||
|
void *fd_opaque;
|
||||||
|
int fd;
|
||||||
|
struct sockaddr_in daddr;
|
||||||
|
char buf[1024];
|
||||||
|
int bufcnt;
|
||||||
|
int bufptr;
|
||||||
|
int max_size;
|
||||||
|
} NetCharDriver;
|
||||||
|
|
||||||
|
static int udp_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
|
||||||
|
{
|
||||||
|
NetCharDriver *s = chr->opaque;
|
||||||
|
|
||||||
|
return sendto(s->fd, buf, len, 0,
|
||||||
|
(struct sockaddr *)&s->daddr, sizeof(struct sockaddr_in));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int udp_chr_read_poll(void *opaque)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = opaque;
|
||||||
|
NetCharDriver *s = chr->opaque;
|
||||||
|
|
||||||
|
s->max_size = s->fd_can_read(s->fd_opaque);
|
||||||
|
|
||||||
|
/* If there were any stray characters in the queue process them
|
||||||
|
* first
|
||||||
|
*/
|
||||||
|
while (s->max_size > 0 && s->bufptr < s->bufcnt) {
|
||||||
|
s->fd_read(s->fd_opaque, &s->buf[s->bufptr], 1);
|
||||||
|
s->bufptr++;
|
||||||
|
s->max_size = s->fd_can_read(s->fd_opaque);
|
||||||
|
}
|
||||||
|
return s->max_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void udp_chr_read(void *opaque)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = opaque;
|
||||||
|
NetCharDriver *s = chr->opaque;
|
||||||
|
|
||||||
|
if (s->max_size == 0)
|
||||||
|
return;
|
||||||
|
s->bufcnt = recv(s->fd, s->buf, sizeof(s->buf), 0);
|
||||||
|
s->bufptr = s->bufcnt;
|
||||||
|
if (s->bufcnt <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
s->bufptr = 0;
|
||||||
|
while (s->max_size > 0 && s->bufptr < s->bufcnt) {
|
||||||
|
s->fd_read(s->fd_opaque, &s->buf[s->bufptr], 1);
|
||||||
|
s->bufptr++;
|
||||||
|
s->max_size = s->fd_can_read(s->fd_opaque);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void udp_chr_add_read_handler(CharDriverState *chr,
|
||||||
|
IOCanRWHandler *fd_can_read,
|
||||||
|
IOReadHandler *fd_read, void *opaque)
|
||||||
|
{
|
||||||
|
NetCharDriver *s = chr->opaque;
|
||||||
|
|
||||||
|
if (s->fd >= 0) {
|
||||||
|
s->fd_can_read = fd_can_read;
|
||||||
|
s->fd_read = fd_read;
|
||||||
|
s->fd_opaque = opaque;
|
||||||
|
qemu_set_fd_handler2(s->fd, udp_chr_read_poll,
|
||||||
|
udp_chr_read, NULL, chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_host_port(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;
|
||||||
|
|
||||||
|
chr = qemu_mallocz(sizeof(CharDriverState));
|
||||||
|
if (!chr)
|
||||||
|
goto return_err;
|
||||||
|
s = qemu_mallocz(sizeof(NetCharDriver));
|
||||||
|
if (!s)
|
||||||
|
goto return_err;
|
||||||
|
|
||||||
|
fd = socket(PF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("socket(PF_INET, SOCK_DGRAM)");
|
||||||
|
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 (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
||||||
|
{
|
||||||
|
perror("bind");
|
||||||
|
goto return_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->fd = fd;
|
||||||
|
s->bufcnt = 0;
|
||||||
|
s->bufptr = 0;
|
||||||
|
chr->opaque = s;
|
||||||
|
chr->chr_write = udp_chr_write;
|
||||||
|
chr->chr_add_read_handler = udp_chr_add_read_handler;
|
||||||
|
return chr;
|
||||||
|
|
||||||
|
return_err:
|
||||||
|
if (chr)
|
||||||
|
free(chr);
|
||||||
|
if (s)
|
||||||
|
free(s);
|
||||||
|
if (fd >= 0)
|
||||||
|
closesocket(fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************/
|
||||||
|
/* TCP Net console */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
IOCanRWHandler *fd_can_read;
|
||||||
|
IOReadHandler *fd_read;
|
||||||
|
void *fd_opaque;
|
||||||
|
int fd, listen_fd;
|
||||||
|
int connected;
|
||||||
|
int max_size;
|
||||||
|
} TCPCharDriver;
|
||||||
|
|
||||||
|
static void tcp_chr_accept(void *opaque);
|
||||||
|
|
||||||
|
static int tcp_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
|
||||||
|
{
|
||||||
|
TCPCharDriver *s = chr->opaque;
|
||||||
|
if (s->connected) {
|
||||||
|
return send_all(s->fd, buf, len);
|
||||||
|
} else {
|
||||||
|
/* XXX: indicate an error ? */
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tcp_chr_read_poll(void *opaque)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = opaque;
|
||||||
|
TCPCharDriver *s = chr->opaque;
|
||||||
|
if (!s->connected)
|
||||||
|
return 0;
|
||||||
|
s->max_size = s->fd_can_read(s->fd_opaque);
|
||||||
|
return s->max_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_chr_read(void *opaque)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = opaque;
|
||||||
|
TCPCharDriver *s = chr->opaque;
|
||||||
|
uint8_t buf[1024];
|
||||||
|
int len, size;
|
||||||
|
|
||||||
|
if (!s->connected || s->max_size <= 0)
|
||||||
|
return;
|
||||||
|
len = sizeof(buf);
|
||||||
|
if (len > s->max_size)
|
||||||
|
len = s->max_size;
|
||||||
|
size = recv(s->fd, buf, len, 0);
|
||||||
|
if (size == 0) {
|
||||||
|
/* connection closed */
|
||||||
|
s->connected = 0;
|
||||||
|
if (s->listen_fd >= 0) {
|
||||||
|
qemu_set_fd_handler(s->listen_fd, tcp_chr_accept, NULL, chr);
|
||||||
|
}
|
||||||
|
qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
|
||||||
|
closesocket(s->fd);
|
||||||
|
s->fd = -1;
|
||||||
|
} else if (size > 0) {
|
||||||
|
s->fd_read(s->fd_opaque, buf, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_chr_add_read_handler(CharDriverState *chr,
|
||||||
|
IOCanRWHandler *fd_can_read,
|
||||||
|
IOReadHandler *fd_read, void *opaque)
|
||||||
|
{
|
||||||
|
TCPCharDriver *s = chr->opaque;
|
||||||
|
|
||||||
|
s->fd_can_read = fd_can_read;
|
||||||
|
s->fd_read = fd_read;
|
||||||
|
s->fd_opaque = opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_chr_connect(void *opaque)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = opaque;
|
||||||
|
TCPCharDriver *s = chr->opaque;
|
||||||
|
|
||||||
|
s->connected = 1;
|
||||||
|
qemu_set_fd_handler2(s->fd, tcp_chr_read_poll,
|
||||||
|
tcp_chr_read, NULL, chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_chr_accept(void *opaque)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = opaque;
|
||||||
|
TCPCharDriver *s = chr->opaque;
|
||||||
|
struct sockaddr_in saddr;
|
||||||
|
socklen_t len;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
len = sizeof(saddr);
|
||||||
|
fd = accept(s->listen_fd, (struct sockaddr *)&saddr, &len);
|
||||||
|
if (fd < 0 && errno != EINTR) {
|
||||||
|
return;
|
||||||
|
} else if (fd >= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socket_set_nonblock(fd);
|
||||||
|
s->fd = fd;
|
||||||
|
qemu_set_fd_handler(s->listen_fd, NULL, NULL, NULL);
|
||||||
|
tcp_chr_connect(chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tcp_chr_close(CharDriverState *chr)
|
||||||
|
{
|
||||||
|
TCPCharDriver *s = chr->opaque;
|
||||||
|
if (s->fd >= 0)
|
||||||
|
closesocket(s->fd);
|
||||||
|
if (s->listen_fd >= 0)
|
||||||
|
closesocket(s->listen_fd);
|
||||||
|
qemu_free(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CharDriverState *qemu_chr_open_tcp(const char *host_str,
|
||||||
|
int is_listen)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = NULL;
|
||||||
|
TCPCharDriver *s = NULL;
|
||||||
|
int fd = -1, ret, err, val;
|
||||||
|
struct sockaddr_in saddr;
|
||||||
|
|
||||||
|
if (parse_host_port(&saddr, host_str) < 0)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
chr = qemu_mallocz(sizeof(CharDriverState));
|
||||||
|
if (!chr)
|
||||||
|
goto fail;
|
||||||
|
s = qemu_mallocz(sizeof(TCPCharDriver));
|
||||||
|
if (!s)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0)
|
||||||
|
goto fail;
|
||||||
|
socket_set_nonblock(fd);
|
||||||
|
|
||||||
|
s->connected = 0;
|
||||||
|
s->fd = -1;
|
||||||
|
s->listen_fd = -1;
|
||||||
|
if (is_listen) {
|
||||||
|
/* allow fast reuse */
|
||||||
|
val = 1;
|
||||||
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&val, sizeof(val));
|
||||||
|
|
||||||
|
ret = bind(fd, (struct sockaddr *)&saddr, sizeof(saddr));
|
||||||
|
if (ret < 0)
|
||||||
|
goto fail;
|
||||||
|
ret = listen(fd, 0);
|
||||||
|
if (ret < 0)
|
||||||
|
goto fail;
|
||||||
|
s->listen_fd = fd;
|
||||||
|
qemu_set_fd_handler(s->listen_fd, tcp_chr_accept, NULL, chr);
|
||||||
|
} else {
|
||||||
|
for(;;) {
|
||||||
|
ret = connect(fd, (struct sockaddr *)&saddr, sizeof(saddr));
|
||||||
|
if (ret < 0) {
|
||||||
|
err = socket_error();
|
||||||
|
if (err == EINTR || err == EWOULDBLOCK) {
|
||||||
|
} else if (err == EINPROGRESS) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s->connected = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s->fd = fd;
|
||||||
|
if (s->connected)
|
||||||
|
tcp_chr_connect(chr);
|
||||||
|
else
|
||||||
|
qemu_set_fd_handler(s->fd, NULL, tcp_chr_connect, chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
chr->opaque = s;
|
||||||
|
chr->chr_write = tcp_chr_write;
|
||||||
|
chr->chr_add_read_handler = tcp_chr_add_read_handler;
|
||||||
|
chr->chr_close = tcp_chr_close;
|
||||||
|
return chr;
|
||||||
|
fail:
|
||||||
|
if (fd >= 0)
|
||||||
|
closesocket(fd);
|
||||||
|
qemu_free(s);
|
||||||
|
qemu_free(chr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
CharDriverState *qemu_chr_open(const char *filename)
|
CharDriverState *qemu_chr_open(const char *filename)
|
||||||
{
|
{
|
||||||
const char *p;
|
const char *p;
|
||||||
|
@ -2139,6 +2506,15 @@ CharDriverState *qemu_chr_open(const char *filename)
|
||||||
} else if (!strcmp(filename, "null")) {
|
} else if (!strcmp(filename, "null")) {
|
||||||
return qemu_chr_open_null();
|
return qemu_chr_open_null();
|
||||||
} else
|
} else
|
||||||
|
if (strstart(filename, "tcp:", &p)) {
|
||||||
|
return qemu_chr_open_tcp(p, 0);
|
||||||
|
} else
|
||||||
|
if (strstart(filename, "tcpl:", &p)) {
|
||||||
|
return qemu_chr_open_tcp(p, 1);
|
||||||
|
} else
|
||||||
|
if (strstart(filename, "udp:", &p)) {
|
||||||
|
return qemu_chr_open_udp(p);
|
||||||
|
} else
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
if (strstart(filename, "file:", &p)) {
|
if (strstart(filename, "file:", &p)) {
|
||||||
return qemu_chr_open_file_out(p);
|
return qemu_chr_open_file_out(p);
|
||||||
|
@ -2844,7 +3220,8 @@ static int net_socket_mcast_create(struct sockaddr_in *mcastaddr)
|
||||||
socket_set_nonblock(fd);
|
socket_set_nonblock(fd);
|
||||||
return fd;
|
return fd;
|
||||||
fail:
|
fail:
|
||||||
if (fd>=0) close(fd);
|
if (fd >= 0)
|
||||||
|
closesocket(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2972,7 +3349,7 @@ static void net_socket_accept(void *opaque)
|
||||||
}
|
}
|
||||||
s1 = net_socket_fd_init(s->vlan, fd, 1);
|
s1 = net_socket_fd_init(s->vlan, fd, 1);
|
||||||
if (!s1) {
|
if (!s1) {
|
||||||
close(fd);
|
closesocket(fd);
|
||||||
} else {
|
} else {
|
||||||
snprintf(s1->vc->info_str, sizeof(s1->vc->info_str),
|
snprintf(s1->vc->info_str, sizeof(s1->vc->info_str),
|
||||||
"socket: connection from %s:%d",
|
"socket: connection from %s:%d",
|
||||||
|
|
Loading…
Reference in New Issue