- add websocket support

- socket: make 'fd' incompatible with 'reconnect'
 - fix a websocket leak
 - unrelated editorconfig patch that missed -trivial (included for
   convenience)
 - v2: fix commit author field
 -----BEGIN PGP SIGNATURE-----
 
 iQIcBAABAgAGBQJb2rgLAAoJENro4Ql1lpzleAkP/3DTVq6lXfDduRqWYYGLRnpM
 ot5ozi5l9PIDyPe1LbAPC00Yk/5jgRUunc7APRaxtzR3+h7LTyBnyTUde/lDe5qe
 GJ9i1Bz/GWvd4UA800ZNq/9bdmt0I0ti+J/aHNy1EjPGGjSsQyU42s/Gew35uMk0
 ypsKfrU/e0/oq4VB3gLDBaOlfNVG+wBCjrhWWv4FKHXUVE8gUx/oMO4MaQD0OMMD
 9nPW8AXn8wCrIIbazJMSoMoH+GmBQYwBPFRm4Y1bfrDbfMKgLQ39nZ/7SLEPWrPI
 V+taEJ4s4fGn9DoVAtA64Hma4ALq7qelbEktNGIQXY9DfNXVo5h9HD8aA/UErOI5
 VyUAYKX2g292OyWrwUta90LP3faUxlGS13au6oX51Df1Adxm/5XlfZDljB0Ii/Jp
 pAkjVH2mx5iAX2+g4Hb3uoGSQkfIEZnLchTq7MPq2DziJi/UBhg70vwjM4lMCTpd
 yzK9OKQxLmY0DDc2QqLqShIZpJaOift5/uG4htDOToqVHKKbD5/xyLkR3Gqrw9Gk
 7rnajkOW4Qf1WqYE/9cqBPvKCU9pPz9TXeLxtuYS3vw/RANdKgevlCjt4RrwGHG8
 zxoHBhhqIsbYOGqCawWbqhEOP4A6Zbq3rAje7TK59myZ/nWG677TKD7D1sy3fS/J
 2PupX8Dw8rn1eftqrBT0
 =orA3
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/elmarco/tags/chrdev-pull-request' into staging

- add websocket support
- socket: make 'fd' incompatible with 'reconnect'
- fix a websocket leak
- unrelated editorconfig patch that missed -trivial (included for
  convenience)
- v2: fix commit author field

# gpg: Signature made Thu 01 Nov 2018 08:23:39 GMT
# gpg:                using RSA key DAE8E10975969CE5
# gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>"
# gpg:                 aka "Marc-André Lureau <marcandre.lureau@gmail.com>"
# Primary key fingerprint: 87A9 BD93 3F87 C606 D276  F62D DAE8 E109 7596 9CE5

* remotes/elmarco/tags/chrdev-pull-request:
  editorconfig: set emacs mode
  tests/test-char: Check websocket chardev functionality
  chardev: Add websocket support
  chardev/char-socket: Function headers refactoring
  char-socket: make 'fd' incompatible with 'reconnect'
  char-socket: correctly set has_reconnect when parsing QemuOpts
  websock: fix handshake leak

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2018-11-02 10:53:00 +00:00
commit 2959fb7fe5
7 changed files with 257 additions and 37 deletions

View File

@ -1,4 +1,10 @@
# http://editorconfig.org # EditorConfig is a file format and collection of text editor plugins
# for maintaining consistent coding styles between different editors
# and IDEs. Most popular editors support this either natively or via
# plugin.
#
# Check https://editorconfig.org for details.
root = true root = true
[*] [*]
@ -6,10 +12,23 @@ end_of_line = lf
insert_final_newline = true insert_final_newline = true
charset = utf-8 charset = utf-8
[*.mak]
indent_style = tab
indent_size = 8
file_type_emacs = makefile
[Makefile*] [Makefile*]
indent_style = tab indent_style = tab
indent_size = 8 indent_size = 8
file_type_emacs = makefile
[*.{c,h}] [*.{c,h}]
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[*.{vert,frag}]
file_type_emacs = glsl
[*.json]
indent_style = space
file_type_emacs = python

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;
@ -389,30 +392,37 @@ static void tcp_chr_free_connection(Chardev *chr)
s->connected = 0; s->connected = 0;
} }
static char *SocketAddress_to_str(const char *prefix, SocketAddress *addr, static const char *qemu_chr_socket_protocol(SocketChardev *s)
bool is_listen, bool is_telnet)
{ {
switch (addr->type) { if (s->is_telnet) {
return "telnet";
}
return s->is_websock ? "websocket" : "tcp";
}
static char *qemu_chr_socket_address(SocketChardev *s, const char *prefix)
{
switch (s->addr->type) {
case SOCKET_ADDRESS_TYPE_INET: case SOCKET_ADDRESS_TYPE_INET:
return g_strdup_printf("%s%s:%s:%s%s", prefix, return g_strdup_printf("%s%s:%s:%s%s", prefix,
is_telnet ? "telnet" : "tcp", qemu_chr_socket_protocol(s),
addr->u.inet.host, s->addr->u.inet.host,
addr->u.inet.port, s->addr->u.inet.port,
is_listen ? ",server" : ""); s->is_listen ? ",server" : "");
break; break;
case SOCKET_ADDRESS_TYPE_UNIX: case SOCKET_ADDRESS_TYPE_UNIX:
return g_strdup_printf("%sunix:%s%s", prefix, return g_strdup_printf("%sunix:%s%s", prefix,
addr->u.q_unix.path, s->addr->u.q_unix.path,
is_listen ? ",server" : ""); s->is_listen ? ",server" : "");
break; break;
case SOCKET_ADDRESS_TYPE_FD: case SOCKET_ADDRESS_TYPE_FD:
return g_strdup_printf("%sfd:%s%s", prefix, addr->u.fd.str, return g_strdup_printf("%sfd:%s%s", prefix, s->addr->u.fd.str,
is_listen ? ",server" : ""); s->is_listen ? ",server" : "");
break; break;
case SOCKET_ADDRESS_TYPE_VSOCK: case SOCKET_ADDRESS_TYPE_VSOCK:
return g_strdup_printf("%svsock:%s:%s", prefix, return g_strdup_printf("%svsock:%s:%s", prefix,
addr->u.vsock.cid, s->addr->u.vsock.cid,
addr->u.vsock.port); s->addr->u.vsock.port);
default: default:
abort(); abort();
} }
@ -424,8 +434,7 @@ static void update_disconnected_filename(SocketChardev *s)
g_free(chr->filename); g_free(chr->filename);
if (s->addr) { if (s->addr) {
chr->filename = SocketAddress_to_str("disconnected:", s->addr, chr->filename = qemu_chr_socket_address(s, "disconnected:");
s->is_listen, s->is_telnet);
} else { } else {
chr->filename = g_strdup("disconnected:socket"); chr->filename = g_strdup("disconnected:socket");
} }
@ -514,10 +523,12 @@ static int tcp_chr_sync_read(Chardev *chr, const uint8_t *buf, int len)
return size; return size;
} }
static char *sockaddr_to_str(struct sockaddr_storage *ss, socklen_t ss_len, static char *qemu_chr_compute_filename(SocketChardev *s)
struct sockaddr_storage *ps, socklen_t ps_len,
bool is_listen, bool is_telnet)
{ {
struct sockaddr_storage *ss = &s->sioc->localAddr;
struct sockaddr_storage *ps = &s->sioc->remoteAddr;
socklen_t ss_len = s->sioc->localAddrLen;
socklen_t ps_len = s->sioc->remoteAddrLen;
char shost[NI_MAXHOST], sserv[NI_MAXSERV]; char shost[NI_MAXHOST], sserv[NI_MAXSERV];
char phost[NI_MAXHOST], pserv[NI_MAXSERV]; char phost[NI_MAXHOST], pserv[NI_MAXSERV];
const char *left = "", *right = ""; const char *left = "", *right = "";
@ -527,7 +538,7 @@ static char *sockaddr_to_str(struct sockaddr_storage *ss, socklen_t ss_len,
case AF_UNIX: case AF_UNIX:
return g_strdup_printf("unix:%s%s", return g_strdup_printf("unix:%s%s",
((struct sockaddr_un *)(ss))->sun_path, ((struct sockaddr_un *)(ss))->sun_path,
is_listen ? ",server" : ""); s->is_listen ? ",server" : "");
#endif #endif
case AF_INET6: case AF_INET6:
left = "["; left = "[";
@ -539,9 +550,9 @@ static char *sockaddr_to_str(struct sockaddr_storage *ss, socklen_t ss_len,
getnameinfo((struct sockaddr *) ps, ps_len, phost, sizeof(phost), getnameinfo((struct sockaddr *) ps, ps_len, phost, sizeof(phost),
pserv, sizeof(pserv), NI_NUMERICHOST | NI_NUMERICSERV); pserv, sizeof(pserv), NI_NUMERICHOST | NI_NUMERICSERV);
return g_strdup_printf("%s:%s%s%s:%s%s <-> %s%s%s:%s", return g_strdup_printf("%s:%s%s%s:%s%s <-> %s%s%s:%s",
is_telnet ? "telnet" : "tcp", qemu_chr_socket_protocol(s),
left, shost, right, sserv, left, shost, right, sserv,
is_listen ? ",server" : "", s->is_listen ? ",server" : "",
left, phost, right, pserv); left, phost, right, pserv);
default: default:
@ -576,10 +587,7 @@ static void tcp_chr_connect(void *opaque)
SocketChardev *s = SOCKET_CHARDEV(opaque); SocketChardev *s = SOCKET_CHARDEV(opaque);
g_free(chr->filename); g_free(chr->filename);
chr->filename = sockaddr_to_str( chr->filename = qemu_chr_compute_filename(s);
&s->sioc->localAddr, s->sioc->localAddrLen,
&s->sioc->remoteAddr, s->sioc->remoteAddrLen,
s->is_listen, s->is_telnet);
s->connected = 1; s->connected = 1;
update_ioc_handlers(s); update_ioc_handlers(s);
@ -709,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)
{ {
@ -718,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);
@ -804,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;
@ -954,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;
@ -997,6 +1049,10 @@ static void qmp_chardev_open_socket(Chardev *chr,
s->addr = addr = socket_address_flatten(sock->addr); s->addr = addr = socket_address_flatten(sock->addr);
if (sock->has_reconnect && addr->type == SOCKET_ADDRESS_TYPE_FD) {
error_setg(errp, "'reconnect' option is incompatible with 'fd'");
goto error;
}
qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE); qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE);
/* TODO SOCKET_ADDRESS_FD where fd has AF_UNIX */ /* TODO SOCKET_ADDRESS_FD where fd has AF_UNIX */
if (addr->type == SOCKET_ADDRESS_TYPE_UNIX) { if (addr->type == SOCKET_ADDRESS_TYPE_UNIX) {
@ -1067,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");
@ -1115,9 +1172,11 @@ 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 = true; sock->has_reconnect = qemu_opt_find(opts, "reconnect");
sock->reconnect = reconnect; sock->reconnect = reconnect;
sock->tls_creds = g_strdup(tls_creds); sock->tls_creds = g_strdup(tls_creds);

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

@ -163,6 +163,7 @@ qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
responselen = strlen(response); responselen = strlen(response);
buffer_reserve(&ioc->encoutput, responselen); buffer_reserve(&ioc->encoutput, responselen);
buffer_append(&ioc->encoutput, response, responselen); buffer_append(&ioc->encoutput, response, responselen);
g_free(response);
va_end(vargs); va_end(vargs);
} }

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

View File

@ -420,6 +420,130 @@ static void char_socket_fdpass_test(void)
} }
static void websock_server_read(void *opaque, const uint8_t *buf, int size)
{
g_assert_cmpint(size, ==, 5);
g_assert(memcmp(buf, "world", size) == 0);
quit = true;
}
static int websock_server_can_read(void *opaque)
{
return 10;
}
static bool websock_check_http_headers(char *buf, int size)
{
int i;
const char *ans[] = { "HTTP/1.1 101 Switching Protocols\r\n",
"Server: QEMU VNC\r\n",
"Upgrade: websocket\r\n",
"Connection: Upgrade\r\n",
"Sec-WebSocket-Accept:",
"Sec-WebSocket-Protocol: binary\r\n" };
for (i = 0; i < 6; i++) {
if (g_strstr_len(buf, size, ans[i]) == NULL) {
return false;
}
}
return true;
}
static void websock_client_read(void *opaque, const uint8_t *buf, int size)
{
const uint8_t ping[] = { 0x89, 0x85, /* Ping header */
0x07, 0x77, 0x9e, 0xf9, /* Masking key */
0x6f, 0x12, 0xf2, 0x95, 0x68 /* "hello" */ };
const uint8_t binary[] = { 0x82, 0x85, /* Binary header */
0x74, 0x90, 0xb9, 0xdf, /* Masking key */
0x03, 0xff, 0xcb, 0xb3, 0x10 /* "world" */ };
Chardev *chr_client = opaque;
if (websock_check_http_headers((char *) buf, size)) {
qemu_chr_fe_write(chr_client->be, ping, sizeof(ping));
} else if (buf[0] == 0x8a && buf[1] == 0x05) {
g_assert(strncmp((char *) buf + 2, "hello", 5) == 0);
qemu_chr_fe_write(chr_client->be, binary, sizeof(binary));
} else {
g_assert(buf[0] == 0x88 && buf[1] == 0x16);
g_assert(strncmp((char *) buf + 4, "peer requested close", 10) == 0);
quit = true;
}
}
static int websock_client_can_read(void *opaque)
{
return 4096;
}
static void char_websock_test(void)
{
QObject *addr;
QDict *qdict;
const char *port;
char *tmp;
char *handshake_port;
CharBackend be;
CharBackend client_be;
Chardev *chr_client;
Chardev *chr = qemu_chr_new("server",
"websocket:127.0.0.1:0,server,nowait");
const char handshake[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Host: localhost:%s\r\n"
"Origin: http://localhost:%s\r\n"
"Sec-WebSocket-Key: o9JHNiS3/0/0zYE1wa3yIw==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Protocol: binary\r\n\r\n";
const uint8_t close[] = { 0x88, 0x82, /* Close header */
0xef, 0xaa, 0xc5, 0x97, /* Masking key */
0xec, 0x42 /* Status code */ };
addr = object_property_get_qobject(OBJECT(chr), "addr", &error_abort);
qdict = qobject_to(QDict, addr);
port = qdict_get_str(qdict, "port");
tmp = g_strdup_printf("tcp:127.0.0.1:%s", port);
handshake_port = g_strdup_printf(handshake, port, port);
qobject_unref(qdict);
qemu_chr_fe_init(&be, chr, &error_abort);
qemu_chr_fe_set_handlers(&be, websock_server_can_read, websock_server_read,
NULL, NULL, chr, NULL, true);
chr_client = qemu_chr_new("client", tmp);
qemu_chr_fe_init(&client_be, chr_client, &error_abort);
qemu_chr_fe_set_handlers(&client_be, websock_client_can_read,
websock_client_read,
NULL, NULL, chr_client, NULL, true);
g_free(tmp);
qemu_chr_write_all(chr_client,
(uint8_t *) handshake_port,
strlen(handshake_port));
g_free(handshake_port);
main_loop();
g_assert(object_property_get_bool(OBJECT(chr), "connected", &error_abort));
g_assert(object_property_get_bool(OBJECT(chr_client),
"connected", &error_abort));
qemu_chr_write_all(chr_client, close, sizeof(close));
main_loop();
object_unparent(OBJECT(chr_client));
object_unparent(OBJECT(chr));
}
#ifndef _WIN32 #ifndef _WIN32
static void char_pipe_test(void) static void char_pipe_test(void)
{ {
@ -842,6 +966,7 @@ int main(int argc, char **argv)
g_test_add_func("/char/serial", char_serial_test); g_test_add_func("/char/serial", char_serial_test);
#endif #endif
g_test_add_func("/char/hotswap", char_hotswap_test); g_test_add_func("/char/hotswap", char_hotswap_test);
g_test_add_func("/char/websocket", char_websock_test);
return g_test_run(); return g_test_run();
} }