chardev: Add websocket support

New option "websocket" added to allow using WebSocket protocol for
chardev socket backend.
Example:
    -chardev socket,websocket,server,id=...

Signed-off-by: Julia Suvorova <jusual@mail.ru>
Message-Id: <20181018223501.21683-3-jusual@mail.ru>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This commit is contained in:
Julia Suvorova 2018-10-19 01:35:00 +03:00 committed by Marc-André Lureau
parent 4493b6a54c
commit 981b06e744
4 changed files with 77 additions and 11 deletions

View File

@ -26,6 +26,7 @@
#include "chardev/char.h" #include "chardev/char.h"
#include "io/channel-socket.h" #include "io/channel-socket.h"
#include "io/channel-tls.h" #include "io/channel-tls.h"
#include "io/channel-websock.h"
#include "io/net-listener.h" #include "io/net-listener.h"
#include "qemu/error-report.h" #include "qemu/error-report.h"
#include "qemu/option.h" #include "qemu/option.h"
@ -68,6 +69,8 @@ typedef struct {
GSource *telnet_source; GSource *telnet_source;
TCPChardevTelnetInit *telnet_init; TCPChardevTelnetInit *telnet_init;
bool is_websock;
GSource *reconnect_timer; GSource *reconnect_timer;
int64_t reconnect_time; int64_t reconnect_time;
bool connect_err_reported; bool connect_err_reported;
@ -394,7 +397,7 @@ static const char *qemu_chr_socket_protocol(SocketChardev *s)
if (s->is_telnet) { if (s->is_telnet) {
return "telnet"; return "telnet";
} }
return "tcp"; return s->is_websock ? "websocket" : "tcp";
} }
static char *qemu_chr_socket_address(SocketChardev *s, const char *prefix) static char *qemu_chr_socket_address(SocketChardev *s, const char *prefix)
@ -714,6 +717,41 @@ cont:
} }
static void tcp_chr_websock_handshake(QIOTask *task, gpointer user_data)
{
Chardev *chr = user_data;
SocketChardev *s = user_data;
if (qio_task_propagate_error(task, NULL)) {
tcp_chr_disconnect(chr);
} else {
if (s->do_telnetopt) {
tcp_chr_telnet_init(chr);
} else {
tcp_chr_connect(chr);
}
}
}
static void tcp_chr_websock_init(Chardev *chr)
{
SocketChardev *s = SOCKET_CHARDEV(chr);
QIOChannelWebsock *wioc = NULL;
gchar *name;
wioc = qio_channel_websock_new_server(s->ioc);
name = g_strdup_printf("chardev-websocket-server-%s", chr->label);
qio_channel_set_name(QIO_CHANNEL(wioc), name);
g_free(name);
object_unref(OBJECT(s->ioc));
s->ioc = QIO_CHANNEL(wioc);
qio_channel_websock_handshake(wioc, tcp_chr_websock_handshake, chr, NULL);
}
static void tcp_chr_tls_handshake(QIOTask *task, static void tcp_chr_tls_handshake(QIOTask *task,
gpointer user_data) gpointer user_data)
{ {
@ -723,7 +761,9 @@ static void tcp_chr_tls_handshake(QIOTask *task,
if (qio_task_propagate_error(task, NULL)) { if (qio_task_propagate_error(task, NULL)) {
tcp_chr_disconnect(chr); tcp_chr_disconnect(chr);
} else { } else {
if (s->do_telnetopt) { if (s->is_websock) {
tcp_chr_websock_init(chr);
} else if (s->do_telnetopt) {
tcp_chr_telnet_init(chr); tcp_chr_telnet_init(chr);
} else { } else {
tcp_chr_connect(chr); tcp_chr_connect(chr);
@ -809,12 +849,12 @@ static int tcp_chr_new_client(Chardev *chr, QIOChannelSocket *sioc)
if (s->tls_creds) { if (s->tls_creds) {
tcp_chr_tls_init(chr); tcp_chr_tls_init(chr);
} else if (s->is_websock) {
tcp_chr_websock_init(chr);
} else if (s->do_telnetopt) {
tcp_chr_telnet_init(chr);
} else { } else {
if (s->do_telnetopt) { tcp_chr_connect(chr);
tcp_chr_telnet_init(chr);
} else {
tcp_chr_connect(chr);
}
} }
return 0; return 0;
@ -959,13 +999,20 @@ static void qmp_chardev_open_socket(Chardev *chr,
bool is_telnet = sock->has_telnet ? sock->telnet : false; bool is_telnet = sock->has_telnet ? sock->telnet : false;
bool is_tn3270 = sock->has_tn3270 ? sock->tn3270 : false; bool is_tn3270 = sock->has_tn3270 ? sock->tn3270 : false;
bool is_waitconnect = sock->has_wait ? sock->wait : false; bool is_waitconnect = sock->has_wait ? sock->wait : false;
bool is_websock = sock->has_websocket ? sock->websocket : false;
int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0; int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0;
QIOChannelSocket *sioc = NULL; QIOChannelSocket *sioc = NULL;
SocketAddress *addr; SocketAddress *addr;
if (!is_listen && is_websock) {
error_setg(errp, "%s", "Websocket client is not implemented");
goto error;
}
s->is_listen = is_listen; s->is_listen = is_listen;
s->is_telnet = is_telnet; s->is_telnet = is_telnet;
s->is_tn3270 = is_tn3270; s->is_tn3270 = is_tn3270;
s->is_websock = is_websock;
s->do_nodelay = do_nodelay; s->do_nodelay = do_nodelay;
if (sock->tls_creds) { if (sock->tls_creds) {
Object *creds; Object *creds;
@ -1076,6 +1123,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_waitconnect = is_listen && qemu_opt_get_bool(opts, "wait", true);
bool is_telnet = qemu_opt_get_bool(opts, "telnet", false); bool is_telnet = qemu_opt_get_bool(opts, "telnet", false);
bool is_tn3270 = qemu_opt_get_bool(opts, "tn3270", false); bool is_tn3270 = qemu_opt_get_bool(opts, "tn3270", false);
bool is_websock = qemu_opt_get_bool(opts, "websocket", false);
bool do_nodelay = !qemu_opt_get_bool(opts, "delay", true); bool do_nodelay = !qemu_opt_get_bool(opts, "delay", true);
int64_t reconnect = qemu_opt_get_number(opts, "reconnect", 0); int64_t reconnect = qemu_opt_get_number(opts, "reconnect", 0);
const char *path = qemu_opt_get(opts, "path"); const char *path = qemu_opt_get(opts, "path");
@ -1124,6 +1172,8 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
sock->telnet = is_telnet; sock->telnet = is_telnet;
sock->has_tn3270 = true; sock->has_tn3270 = true;
sock->tn3270 = is_tn3270; sock->tn3270 = is_tn3270;
sock->has_websocket = true;
sock->websocket = is_websock;
sock->has_wait = true; sock->has_wait = true;
sock->wait = is_waitconnect; sock->wait = is_waitconnect;
sock->has_reconnect = qemu_opt_find(opts, "reconnect"); sock->has_reconnect = qemu_opt_find(opts, "reconnect");

View File

@ -409,7 +409,8 @@ QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename,
} }
if (strstart(filename, "tcp:", &p) || if (strstart(filename, "tcp:", &p) ||
strstart(filename, "telnet:", &p) || strstart(filename, "telnet:", &p) ||
strstart(filename, "tn3270:", &p)) { strstart(filename, "tn3270:", &p) ||
strstart(filename, "websocket:", &p)) {
if (sscanf(p, "%64[^:]:%32[^,]%n", host, port, &pos) < 2) { if (sscanf(p, "%64[^:]:%32[^,]%n", host, port, &pos) < 2) {
host[0] = 0; host[0] = 0;
if (sscanf(p, ":%32[^,]%n", port, &pos) < 1) if (sscanf(p, ":%32[^,]%n", port, &pos) < 1)
@ -429,6 +430,8 @@ QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename,
qemu_opt_set(opts, "telnet", "on", &error_abort); qemu_opt_set(opts, "telnet", "on", &error_abort);
} else if (strstart(filename, "tn3270:", &p)) { } else if (strstart(filename, "tn3270:", &p)) {
qemu_opt_set(opts, "tn3270", "on", &error_abort); qemu_opt_set(opts, "tn3270", "on", &error_abort);
} else if (strstart(filename, "websocket:", &p)) {
qemu_opt_set(opts, "websocket", "on", &error_abort);
} }
return opts; return opts;
} }
@ -860,6 +863,9 @@ QemuOptsList qemu_chardev_opts = {
},{ },{
.name = "tls-creds", .name = "tls-creds",
.type = QEMU_OPT_STRING, .type = QEMU_OPT_STRING,
},{
.name = "websocket",
.type = QEMU_OPT_BOOL,
},{ },{
.name = "width", .name = "width",
.type = QEMU_OPT_NUMBER, .type = QEMU_OPT_NUMBER,

View File

@ -251,6 +251,8 @@
# sockets (default: false) # sockets (default: false)
# @tn3270: enable tn3270 protocol on server # @tn3270: enable tn3270 protocol on server
# sockets (default: false) (Since: 2.10) # sockets (default: false) (Since: 2.10)
# @websocket: enable websocket protocol on server
# sockets (default: false) (Since: 3.1)
# @reconnect: For a client socket, if a socket is disconnected, # @reconnect: For a client socket, if a socket is disconnected,
# then attempt a reconnect after the given number of seconds. # then attempt a reconnect after the given number of seconds.
# Setting this to zero disables this function. (default: 0) # Setting this to zero disables this function. (default: 0)
@ -265,6 +267,7 @@
'*nodelay' : 'bool', '*nodelay' : 'bool',
'*telnet' : 'bool', '*telnet' : 'bool',
'*tn3270' : 'bool', '*tn3270' : 'bool',
'*websocket' : 'bool',
'*reconnect' : 'int' }, '*reconnect' : 'int' },
'base': 'ChardevCommon' } 'base': 'ChardevCommon' }

View File

@ -2414,9 +2414,9 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
"-chardev help\n" "-chardev help\n"
"-chardev null,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n" "-chardev null,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
"-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\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]\n" " [,server][,nowait][,telnet][,websocket][,reconnect=seconds][,mux=on|off]\n"
" [,logfile=PATH][,logappend=on|off][,tls-creds=ID] (tcp)\n" " [,logfile=PATH][,logappend=on|off][,tls-creds=ID] (tcp)\n"
"-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds]\n" "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,websocket][,reconnect=seconds]\n"
" [,mux=on|off][,logfile=PATH][,logappend=on|off] (unix)\n" " [,mux=on|off][,logfile=PATH][,logappend=on|off] (unix)\n"
"-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n" "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
" [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n" " [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
@ -2544,7 +2544,7 @@ The available backends are:
A void device. This device will not emit any data, and will drop any data it 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. 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][,reconnect=@var{seconds}][,tls-creds=@var{id}] @item -chardev socket,id=@var{id}[,@var{TCP options} or @var{unix options}][,server][,nowait][,telnet][,websocket][,reconnect=@var{seconds}][,tls-creds=@var{id}]
Create a two-way stream socket, which can be either a TCP or a unix socket. A 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 unix socket will be created if @option{path} is specified. Behaviour is
@ -2558,6 +2558,9 @@ connect to a listening socket.
@option{telnet} specifies that traffic on the socket should interpret telnet @option{telnet} specifies that traffic on the socket should interpret telnet
escape sequences. escape sequences.
@option{websocket} specifies that the socket uses WebSocket protocol for
communication.
@option{reconnect} sets the timeout for reconnecting on non-server sockets when @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 the remote end goes away. qemu will delay this many seconds and then attempt
to reconnect. Zero disables reconnecting, and is the default. to reconnect. Zero disables reconnecting, and is the default.
@ -3106,6 +3109,10 @@ 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 sequence. Typically in unix telnet you do it with Control-] and then
type "send break" followed by pressing the enter key. type "send break" followed by pressing the enter key.
@item websocket:@var{host}:@var{port},server[,nowait][,nodelay]
The WebSocket protocol is used instead of raw tcp socket. The port acts as
a WebSocket server. Client mode is not supported.
@item unix:@var{path}[,server][,nowait][,reconnect=@var{seconds}] @item unix:@var{path}[,server][,nowait][,reconnect=@var{seconds}]
A unix domain socket is used instead of a tcp socket. The option works the 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 same as if you had specified @code{-serial tcp} except the unix domain socket