Commit cb42a870 authored by Gerd Hoffmann's avatar Gerd Hoffmann
Browse files

spice: add qmp 'query-spice' and hmp 'info spice' commands.



The patch adds a 'query-spice' monitor command which returns
informations about the spice server configuration and also a list of
channel connections.

Signed-off-by: default avatarGerd Hoffmann <kraxel@redhat.com>
parent 6f8c63fb
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -2581,6 +2581,16 @@ static const mon_cmd_t info_cmds[] = {
        .user_print = do_info_vnc_print,
        .mhandler.info_new = do_info_vnc,
    },
#if defined(CONFIG_SPICE)
    {
        .name       = "spice",
        .args_type  = "",
        .params     = "",
        .help       = "show the spice server status",
        .user_print = do_info_spice_print,
        .mhandler.info_new = do_info_spice,
    },
#endif
    {
        .name       = "name",
        .args_type  = "",
@@ -2768,6 +2778,16 @@ static const mon_cmd_t qmp_query_cmds[] = {
        .user_print = do_info_vnc_print,
        .mhandler.info_new = do_info_vnc,
    },
#if defined(CONFIG_SPICE)
    {
        .name       = "spice",
        .args_type  = "",
        .params     = "",
        .help       = "show the spice server status",
        .user_print = do_info_spice_print,
        .mhandler.info_new = do_info_spice,
    },
#endif
    {
        .name       = "name",
        .args_type  = "",
+70 −0
Original line number Diff line number Diff line
@@ -1438,6 +1438,76 @@ Example:

EQMP

SQMP
query-spice
-----------

Show SPICE server information.

Return a json-object with server information. Connected clients are returned
as a json-array of json-objects.

The main json-object contains the following:

- "enabled": true or false (json-bool)
- "host": server's IP address (json-string)
- "port": server's port number (json-int, optional)
- "tls-port": server's port number (json-int, optional)
- "auth": authentication method (json-string)
         - Possible values: "none", "spice"
- "channels": a json-array of all active channels clients

Channels are described by a json-object, each one contain the following:

- "host": client's IP address (json-string)
- "family": address family (json-string)
         - Possible values: "ipv4", "ipv6", "unix", "unknown"
- "port": client's port number (json-string)
- "connection-id": spice connection id.  All channels with the same id
                   belong to the same spice session (json-int)
- "channel-type": channel type.  "1" is the main control channel, filter for
                  this one if you want track spice sessions only (json-int)
- "channel-id": channel id.  Usually "0", might be different needed when
                multiple channels of the same type exist, such as multiple
                display channels in a multihead setup (json-int)
- "tls": whevener the channel is encrypted (json-bool)

Example:

-> { "execute": "query-spice" }
<- {
      "return": {
         "enabled": true,
         "auth": "spice",
         "port": 5920,
         "tls-port": 5921,
         "host": "0.0.0.0",
         "channels": [
            {
               "port": "54924",
               "family": "ipv4",
               "channel-type": 1,
               "connection-id": 1804289383,
               "host": "127.0.0.1",
               "channel-id": 0,
               "tls": true
            },
            {
               "port": "36710",
               "family": "ipv4",
               "channel-type": 4,
               "connection-id": 1804289383,
               "host": "127.0.0.1",
               "channel-id": 0,
               "tls": false
            },
            [ ... more channels follow ... ]
         ]
      }
   }

EQMP

SQMP
query-name
----------
+3 −0
Original line number Diff line number Diff line
@@ -33,6 +33,9 @@ void qemu_spice_audio_init(void);
void qemu_spice_display_init(DisplayState *ds);
int qemu_spice_add_interface(SpiceBaseInstance *sin);

void do_info_spice_print(Monitor *mon, const QObject *data);
void do_info_spice(Monitor *mon, QObject **ret_data);

#else  /* CONFIG_SPICE */

#define using_spice 0
+136 −0
Original line number Diff line number Diff line
@@ -131,6 +131,36 @@ static void watch_remove(SpiceWatch *watch)

#if SPICE_INTERFACE_CORE_MINOR >= 3

typedef struct ChannelList ChannelList;
struct ChannelList {
    SpiceChannelEventInfo *info;
    QTAILQ_ENTRY(ChannelList) link;
};
static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list);

static void channel_list_add(SpiceChannelEventInfo *info)
{
    ChannelList *item;

    item = qemu_mallocz(sizeof(*item));
    item->info = info;
    QTAILQ_INSERT_TAIL(&channel_list, item, link);
}

static void channel_list_del(SpiceChannelEventInfo *info)
{
    ChannelList *item;

    QTAILQ_FOREACH(item, &channel_list, link) {
        if (item->info != info) {
            continue;
        }
        QTAILQ_REMOVE(&channel_list, item, link);
        qemu_free(item);
        return;
    }
}

static void add_addr_info(QDict *dict, struct sockaddr *addr, int len)
{
    char host[NI_MAXHOST], port[NI_MAXSERV];
@@ -155,6 +185,22 @@ static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info)
    qdict_put(dict, "tls", qbool_from_int(tls));
}

static QList *channel_list_get(void)
{
    ChannelList *item;
    QList *list;
    QDict *dict;

    list = qlist_new();
    QTAILQ_FOREACH(item, &channel_list, link) {
        dict = qdict_new();
        add_addr_info(dict, &item->info->paddr, item->info->plen);
        add_channel_info(dict, item->info);
        qlist_append(list, dict);
    }
    return list;
}

static void channel_event(int event, SpiceChannelEventInfo *info)
{
    static const int qevent[] = {
@@ -174,6 +220,10 @@ static void channel_event(int event, SpiceChannelEventInfo *info)
    if (event == SPICE_CHANNEL_EVENT_INITIALIZED) {
        qdict_put(server, "auth", qstring_from_str(auth));
        add_channel_info(client, info);
        channel_list_add(info);
    }
    if (event == SPICE_CHANNEL_EVENT_DISCONNECTED) {
        channel_list_del(info);
    }

    data = qobject_from_jsonf("{ 'client': %p, 'server': %p }",
@@ -278,6 +328,92 @@ static const char *wan_compression_names[] = {

/* functions for the rest of qemu */

static void info_spice_iter(QObject *obj, void *opaque)
{
    QDict *client;
    Monitor *mon = opaque;

    client = qobject_to_qdict(obj);
    monitor_printf(mon, "Channel:\n");
    monitor_printf(mon, "     address: %s:%s%s\n",
                   qdict_get_str(client, "host"),
                   qdict_get_str(client, "port"),
                   qdict_get_bool(client, "tls") ? " [tls]" : "");
    monitor_printf(mon, "     session: %" PRId64 "\n",
                   qdict_get_int(client, "connection-id"));
    monitor_printf(mon, "     channel: %d:%d\n",
                   (int)qdict_get_int(client, "channel-type"),
                   (int)qdict_get_int(client, "channel-id"));
}

void do_info_spice_print(Monitor *mon, const QObject *data)
{
    QDict *server;
    QList *channels;
    const char *host;
    int port;

    server = qobject_to_qdict(data);
    if (qdict_get_bool(server, "enabled") == 0) {
        monitor_printf(mon, "Server: disabled\n");
        return;
    }

    monitor_printf(mon, "Server:\n");
    host = qdict_get_str(server, "host");
    port = qdict_get_try_int(server, "port", -1);
    if (port != -1) {
        monitor_printf(mon, "     address: %s:%d\n", host, port);
    }
    port = qdict_get_try_int(server, "tls-port", -1);
    if (port != -1) {
        monitor_printf(mon, "     address: %s:%d [tls]\n", host, port);
    }
    monitor_printf(mon, "        auth: %s\n", qdict_get_str(server, "auth"));

    channels = qdict_get_qlist(server, "channels");
    if (qlist_empty(channels)) {
        monitor_printf(mon, "Channels: none\n");
    } else {
        qlist_iter(channels, info_spice_iter, mon);
    }
}

void do_info_spice(Monitor *mon, QObject **ret_data)
{
    QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
    QDict *server;
    QList *clist;
    const char *addr;
    int port, tls_port;

    if (!spice_server) {
        *ret_data = qobject_from_jsonf("{ 'enabled': false }");
        return;
    }

    addr = qemu_opt_get(opts, "addr");
    port = qemu_opt_get_number(opts, "port", 0);
    tls_port = qemu_opt_get_number(opts, "tls-port", 0);
    clist = channel_list_get();

    server = qdict_new();
    qdict_put(server, "enabled", qbool_from_int(true));
    qdict_put(server, "auth", qstring_from_str(auth));
    qdict_put(server, "host", qstring_from_str(addr ? addr : "0.0.0.0"));
    if (port) {
        qdict_put(server, "port", qint_from_int(port));
    }
    if (tls_port) {
        qdict_put(server, "tls-port", qint_from_int(tls_port));
    }
    if (clist) {
        qdict_put(server, "channels", clist);
    }

    *ret_data = QOBJECT(server);
}

static int add_channel(const char *name, const char *value, void *opaque)
{
    int security = 0;