Skip to content

Commit

Permalink
qemu-char: Add reconnecting to client sockets
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
cminyard authored and bonzini committed Oct 4, 2014
1 parent 16cc4ff commit 5dd1f02
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 17 deletions.
15 changes: 10 additions & 5 deletions qapi-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
78 changes: 73 additions & 5 deletions qemu-char.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -3013,14 +3033,29 @@ 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;
int fd;

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) {
Expand Down Expand Up @@ -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");
Expand All @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand All @@ -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));
Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 13 additions & 7 deletions qemu-options.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down

0 comments on commit 5dd1f02

Please sign in to comment.