qemu-char: Add reconnecting to client sockets

Adds a "reconnect" option to socket backends that gives a reconnect
timeout.  This only applies to client sockets.  If the other end
of a socket closes the connection, qemu will attempt to reconnect
after the given number of seconds.

Signed-off-by: Corey Minyard <cminyard@mvista.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Corey Minyard 2014-10-02 11:17:37 -05:00 committed by Paolo Bonzini
parent 16cc4ffe34
commit 5dd1f02b4b
3 changed files with 96 additions and 17 deletions

View File

@ -2651,14 +2651,19 @@
# @nodelay: #optional set TCP_NODELAY socket option (default: false)
# @telnet: #optional enable telnet protocol on server
# sockets (default: false)
# @reconnect: #optional For a client socket, if a socket is disconnected,
# then attempt a reconnect after the given number of seconds.
# Setting this to zero disables this function. (default: 0)
# (Since: 2.2)
#
# Since: 1.4
##
{ 'type': 'ChardevSocket', 'data': { 'addr' : 'SocketAddress',
'*server' : 'bool',
'*wait' : 'bool',
'*nodelay' : 'bool',
'*telnet' : 'bool' } }
{ 'type': 'ChardevSocket', 'data': { 'addr' : 'SocketAddress',
'*server' : 'bool',
'*wait' : 'bool',
'*nodelay' : 'bool',
'*telnet' : 'bool',
'*reconnect' : 'int' } }
##
# @ChardevUdp:

View File

@ -2501,8 +2501,21 @@ typedef struct {
SocketAddress *addr;
bool is_listen;
bool is_telnet;
guint reconnect_timer;
int64_t reconnect_time;
} TCPCharDriver;
static gboolean socket_reconnect_timeout(gpointer opaque);
static void qemu_chr_socket_restart_timer(CharDriverState *chr)
{
TCPCharDriver *s = chr->opaque;
assert(s->connected == 0);
s->reconnect_timer = g_timeout_add_seconds(s->reconnect_time,
socket_reconnect_timeout, chr);
}
static gboolean tcp_chr_accept(GIOChannel *chan, GIOCondition cond, void *opaque);
#ifndef _WIN32
@ -2784,6 +2797,9 @@ static void tcp_chr_disconnect(CharDriverState *chr)
SocketAddress_to_str(chr->filename, CHR_MAX_FILENAME_SIZE,
"disconnected:", s->addr, s->is_listen, s->is_telnet);
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
if (s->reconnect_time) {
qemu_chr_socket_restart_timer(chr);
}
}
static gboolean tcp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
@ -2964,6 +2980,10 @@ static void tcp_chr_close(CharDriverState *chr)
TCPCharDriver *s = chr->opaque;
int i;
if (s->reconnect_timer) {
g_source_remove(s->reconnect_timer);
s->reconnect_timer = 0;
}
qapi_free_SocketAddress(s->addr);
if (s->fd >= 0) {
remove_fd_in_watch(chr);
@ -3013,6 +3033,18 @@ static void qemu_chr_finish_socket_connection(CharDriverState *chr, int fd)
}
}
static void qemu_chr_socket_connected(int fd, void *opaque)
{
CharDriverState *chr = opaque;
if (fd < 0) {
qemu_chr_socket_restart_timer(chr);
return;
}
qemu_chr_finish_socket_connection(chr, fd);
}
static bool qemu_chr_open_socket_fd(CharDriverState *chr, Error **errp)
{
TCPCharDriver *s = chr->opaque;
@ -3020,7 +3052,10 @@ static bool qemu_chr_open_socket_fd(CharDriverState *chr, Error **errp)
if (s->is_listen) {
fd = socket_listen(s->addr, errp);
} else {
} else if (s->reconnect_time) {
fd = socket_connect(s->addr, errp, qemu_chr_socket_connected, chr);
return fd >= 0;
} else {
fd = socket_connect(s->addr, errp, NULL, NULL);
}
if (fd < 0) {
@ -3448,6 +3483,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
bool is_waitconnect = is_listen && qemu_opt_get_bool(opts, "wait", true);
bool is_telnet = qemu_opt_get_bool(opts, "telnet", false);
bool do_nodelay = !qemu_opt_get_bool(opts, "delay", true);
int64_t reconnect = qemu_opt_get_number(opts, "reconnect", 0);
const char *path = qemu_opt_get(opts, "path");
const char *host = qemu_opt_get(opts, "host");
const char *port = qemu_opt_get(opts, "port");
@ -3474,6 +3510,8 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
backend->socket->telnet = is_telnet;
backend->socket->has_wait = true;
backend->socket->wait = is_waitconnect;
backend->socket->has_reconnect = true;
backend->socket->reconnect = reconnect;
addr = g_new0(SocketAddress, 1);
if (path) {
@ -3872,6 +3910,9 @@ QemuOptsList qemu_chardev_opts = {
},{
.name = "delay",
.type = QEMU_OPT_BOOL,
},{
.name = "reconnect",
.type = QEMU_OPT_NUMBER,
},{
.name = "telnet",
.type = QEMU_OPT_BOOL,
@ -4016,6 +4057,26 @@ static CharDriverState *qmp_chardev_open_parallel(ChardevHostdev *parallel,
#endif /* WIN32 */
static gboolean socket_reconnect_timeout(gpointer opaque)
{
CharDriverState *chr = opaque;
TCPCharDriver *s = chr->opaque;
Error *err;
s->reconnect_timer = 0;
if (chr->be_open) {
return false;
}
if (!qemu_chr_open_socket_fd(chr, &err)) {
error_report("Unable to connect to char device %s\n", chr->label);
qemu_chr_socket_restart_timer(chr);
}
return false;
}
static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
Error **errp)
{
@ -4026,6 +4087,7 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
bool is_listen = sock->has_server ? sock->server : true;
bool is_telnet = sock->has_telnet ? sock->telnet : false;
bool is_waitconnect = sock->has_wait ? sock->wait : false;
int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0;
chr = qemu_chr_alloc();
s = g_malloc0(sizeof(TCPCharDriver));
@ -4058,13 +4120,19 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
if (is_telnet) {
s->do_telnetopt = 1;
}
} else if (reconnect > 0) {
s->reconnect_time = reconnect;
}
if (!qemu_chr_open_socket_fd(chr, errp)) {
g_free(s);
g_free(chr->filename);
g_free(chr);
return NULL;
if (s->reconnect_time) {
qemu_chr_socket_restart_timer(chr);
} else {
g_free(s);
g_free(chr->filename);
g_free(chr);
return NULL;
}
}
if (is_listen && is_waitconnect) {

View File

@ -1930,9 +1930,9 @@ ETEXI
DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
"-chardev null,id=id[,mux=on|off]\n"
"-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay]\n"
" [,server][,nowait][,telnet][,mux=on|off] (tcp)\n"
"-chardev socket,id=id,path=path[,server][,nowait][,telnet],[mux=on|off] (unix)\n"
"-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\n"
" [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (tcp)\n"
"-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (unix)\n"
"-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
" [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
"-chardev msmouse,id=id[,mux=on|off]\n"
@ -2004,7 +2004,7 @@ Options to each backend are described below.
A void device. This device will not emit any data, and will drop any data it
receives. The null backend does not take any options.
@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet]
@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}]
Create a two-way stream socket, which can be either a TCP or a unix socket. A
unix socket will be created if @option{path} is specified. Behaviour is
@ -2018,6 +2018,10 @@ connect to a listening socket.
@option{telnet} specifies that traffic on the socket should interpret telnet
escape sequences.
@option{reconnect} sets the timeout for reconnecting on non-server sockets when
the remote end goes away. qemu will delay this many seconds and then attempt
to reconnect. Zero disables reconnecting, and is the default.
TCP and unix socket options are given below:
@table @option
@ -2687,14 +2691,16 @@ telnet on port 5555 to access the QEMU port.
localhost 5555
@end table
@item tcp:[@var{host}]:@var{port}[,@var{server}][,nowait][,nodelay]
@item tcp:[@var{host}]:@var{port}[,@var{server}][,nowait][,nodelay][,reconnect=@var{seconds}]
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. The @code{nodelay} option disables the Nagle buffering
algorithm. If @var{host} is omitted, 0.0.0.0 is assumed. Only
algorithm. The @code{reconnect} option only applies if @var{noserver} is
set, if the connection goes down it will attempt to reconnect at the
given interval. 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
@ -2715,7 +2721,7 @@ 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 unix:@var{path}[,server][,nowait]
@item unix:@var{path}[,server][,nowait][,reconnect=@var{seconds}]
A unix domain socket is used instead of a tcp socket. The option works the
same as if you had specified @code{-serial tcp} except the unix domain socket
@var{path} is used for connections.