[Spice-devel] [PATCH migration 05/19] server, proto: tell the clients to connect to the migration target before migraton starts
Alon Levy
alevy at redhat.com
Tue Nov 1 02:07:09 PDT 2011
On Wed, Oct 12, 2011 at 12:38:55PM +0200, Yonit Halperin wrote:
> (1) send SPICE_MSG_MAIN_MIGRATE_BEGIN upon spice_server_migrate_connect
> (to all the clients that support it)
> (2) wait for SPICE_MSGC_MAIN_MIGRATE_(CONNECTED|CONNECT_ERROR) from all the relevant clients,
> or a timeout, in order to complete client_migrate_info monitor command
> (cherry picked from commit 5560c56ef05c74da5e0e0825dc1f134019593cad branch 0.8;
> Was modified to support the separation of main channel from reds, and multiple clients)
>
Looks ok.
ACK.
> Conflicts:
>
> server/reds.c
>
> Signed-off-by: Yonit Halperin <yhalperi at redhat.com>
> ---
> common/messages.h | 2 +
> server/main_channel.c | 164 ++++++++++++++++++++++++++++---------------------
> server/main_channel.h | 12 +++-
> server/red_channel.h | 2 +-
> server/reds.c | 129 +++++++++++++++++---------------------
> server/reds.h | 14 +++-
> spice.proto | 5 +-
> 7 files changed, 176 insertions(+), 152 deletions(-)
>
> diff --git a/common/messages.h b/common/messages.h
> index 8151dc0..a54190f 100644
> --- a/common/messages.h
> +++ b/common/messages.h
> @@ -66,6 +66,8 @@ typedef struct SpiceMsgMainMigrationBegin {
> uint16_t pub_key_type;
> uint32_t pub_key_size;
> uint8_t *pub_key_data;
> + uint32_t cert_subject_size;
> + uint8_t *cert_subject_data;
> } SpiceMsgMainMigrationBegin;
>
> typedef struct SpiceMsgMainMigrationSwitchHost {
> diff --git a/server/main_channel.c b/server/main_channel.c
> index 49028b3..5b16b30 100644
> --- a/server/main_channel.c
> +++ b/server/main_channel.c
> @@ -110,16 +110,6 @@ typedef struct NotifyPipeItem {
> int mess_len;
> } NotifyPipeItem;
>
> -typedef struct MigrateBeginPipeItem {
> - PipeItem base;
> - int port;
> - int sport;
> - char *host;
> - uint16_t cert_pub_key_type;
> - uint32_t cert_pub_key_len;
> - uint8_t *cert_pub_key;
> -} MigrateBeginPipeItem;
> -
> typedef struct MultiMediaTimePipeItem {
> PipeItem base;
> int time;
> @@ -137,6 +127,8 @@ struct MainChannelClient {
> SpiceTimer *ping_timer;
> int ping_interval;
> #endif
> + int mig_wait_connect;
> + int mig_connect_ok;
> };
>
> enum NetTestStage {
> @@ -283,33 +275,6 @@ static PipeItem *main_notify_item_new(RedChannelClient *rcc, void *data, int num
> return &item->base;
> }
>
> -typedef struct MigrateBeginItemInfo {
> - int port;
> - int sport;
> - char *host;
> - uint16_t cert_pub_key_type;
> - uint32_t cert_pub_key_len;
> - uint8_t *cert_pub_key;
> -} MigrateBeginItemInfo;
> -
> -// TODO: MC: migration is not tested at all with multiclient.
> -static PipeItem *main_migrate_begin_item_new(
> - RedChannelClient *rcc, void *data, int num)
> -{
> - MigrateBeginPipeItem *item = spice_malloc(sizeof(MigrateBeginPipeItem));
> - MigrateBeginItemInfo *info = data;
> -
> - red_channel_pipe_item_init(rcc->channel, &item->base,
> - SPICE_MSG_MAIN_MIGRATE_BEGIN);
> - item->port = info->port;
> - item->sport = info->sport;
> - item->host = info->host;
> - item->cert_pub_key_type = info->cert_pub_key_type;
> - item->cert_pub_key_len = info->cert_pub_key_len;
> - item->cert_pub_key = info->cert_pub_key;
> - return &item->base;
> -}
> -
> static PipeItem *main_multi_media_time_item_new(
> RedChannelClient *rcc, void *data, int num)
> {
> @@ -550,35 +515,23 @@ static void main_channel_marshall_notify(SpiceMarshaller *m, NotifyPipeItem *ite
> spice_marshaller_add(m, item->mess, item->mess_len + 1);
> }
>
> -void main_channel_push_migrate_begin(MainChannel *main_chan, int port, int sport,
> - char *host, uint16_t cert_pub_key_type, uint32_t cert_pub_key_len,
> - uint8_t *cert_pub_key)
> -{
> - MigrateBeginItemInfo info = {
> - .port =port,
> - .sport = sport,
> - .host = host,
> - .cert_pub_key_type = cert_pub_key_type,
> - .cert_pub_key_len = cert_pub_key_len,
> - .cert_pub_key = cert_pub_key,
> - };
> -
> - red_channel_pipes_new_add_push(&main_chan->base,
> - main_migrate_begin_item_new, &info);
> -}
> -
> -static void main_channel_marshall_migrate_begin(SpiceMarshaller *m,
> - MigrateBeginPipeItem *item)
> +static void main_channel_marshall_migrate_begin(SpiceMarshaller *m, RedChannelClient *rcc)
> {
> SpiceMsgMainMigrationBegin migrate;
> -
> - migrate.port = item->port;
> - migrate.sport = item->sport;
> - migrate.host_size = strlen(item->host) + 1;
> - migrate.host_data = (uint8_t *)item->host;
> - migrate.pub_key_type = item->cert_pub_key_type;
> - migrate.pub_key_size = item->cert_pub_key_len;
> - migrate.pub_key_data = item->cert_pub_key;
> + MainChannel *main_ch;
> +
> + main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> + migrate.port = main_ch->mig_target.port;
> + migrate.sport = main_ch->mig_target.sport;
> + migrate.host_size = strlen(main_ch->mig_target.host) + 1;
> + migrate.host_data = (uint8_t *)main_ch->mig_target.host;
> + if (main_ch->mig_target.cert_subject) {
> + migrate.cert_subject_size = strlen(main_ch->mig_target.cert_subject) + 1;
> + migrate.cert_subject_data = (uint8_t *)main_ch->mig_target.cert_subject;
> + } else {
> + migrate.cert_subject_size = 0;
> + migrate.cert_subject_data = NULL;
> + }
> spice_marshall_msg_main_migrate_begin(m, &migrate);
> }
>
> @@ -699,8 +652,7 @@ static void main_channel_send_item(RedChannelClient *rcc, PipeItem *base)
> main_channel_marshall_migrate(m);
> break;
> case SPICE_MSG_MAIN_MIGRATE_BEGIN:
> - main_channel_marshall_migrate_begin(m,
> - SPICE_CONTAINEROF(base, MigrateBeginPipeItem, base));
> + main_channel_marshall_migrate_begin(m, rcc);
> break;
> case SPICE_MSG_MAIN_MULTI_MEDIA_TIME:
> main_channel_marshall_multi_media_time(m,
> @@ -741,6 +693,26 @@ static void main_channel_release_pipe_item(RedChannelClient *rcc,
> free(base);
> }
>
> +void main_channel_client_handle_migrate_connected(MainChannelClient *mcc, int success)
> +{
> + red_printf("client %p connected: %d", mcc->base.client, success);
> + if (mcc->mig_wait_connect) {
> + MainChannel *main_channel = SPICE_CONTAINEROF(mcc->base.channel, MainChannel, base);
> +
> + mcc->mig_wait_connect = FALSE;
> + mcc->mig_connect_ok = success;
> + ASSERT(main_channel->num_clients_mig_wait);
> + if (!--main_channel->num_clients_mig_wait) {
> + reds_on_main_migrate_connected();
> + }
> + } else {
> + if (success) {
> + red_printf("client %p MIGRATE_CANCEL", mcc->base.client);
> + red_channel_client_pipe_add_type(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL);
> + }
> + }
> +}
> +
> static int main_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint16_t type, void *message)
> {
> MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
> @@ -764,12 +736,10 @@ static int main_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint
> main_channel_push_channels(mcc);
> break;
> case SPICE_MSGC_MAIN_MIGRATE_CONNECTED:
> - red_printf("connected");
> - reds_on_main_migrate_connected();
> + main_channel_client_handle_migrate_connected(mcc, TRUE);
> break;
> case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR:
> - red_printf("mig connect error");
> - reds_on_main_migrate_connect_error();
> + main_channel_client_handle_migrate_connected(mcc, FALSE);
> break;
> case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST:
> reds_on_main_mouse_mode_request(message, size);
> @@ -1001,6 +971,58 @@ MainChannel* main_channel_init(void)
> main_channel_handle_parsed,
> &channel_cbs);
> ASSERT(channel);
> -
> + red_channel_set_cap(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
> return (MainChannel *)channel;
> }
> +
> +RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc)
> +{
> + ASSERT(mcc);
> + return &mcc->base;
> +}
> +
> +int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target)
> +{
> + RingItem *client_link;
> +
> + ASSERT(mig_target);
> + free(main_channel->mig_target.host);
> + main_channel->mig_target.host = strdup(mig_target->host);
> + free(main_channel->mig_target.cert_subject);
> + if (mig_target->cert_subject) {
> + main_channel->mig_target.cert_subject = strdup(mig_target->cert_subject);
> + }
> + main_channel->mig_target.port = mig_target->port;
> + main_channel->mig_target.sport = mig_target->sport;
> +
> + main_channel->num_clients_mig_wait = 0;
> +
> + RING_FOREACH(client_link, &main_channel->base.clients) {
> + MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link);
> + if (red_channel_client_test_remote_cap(&mcc->base,
> + SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) {
> + red_channel_client_pipe_add_type(&mcc->base, SPICE_MSG_MAIN_MIGRATE_BEGIN);
> + mcc->mig_wait_connect = TRUE;
> + mcc->mig_connect_ok = FALSE;
> + main_channel->num_clients_mig_wait++;
> + }
> + }
> + return main_channel->num_clients_mig_wait;
> +}
> +
> +void main_channel_migrate_cancel_wait(MainChannel *main_chan)
> +{
> + RingItem *client_link;
> +
> + RING_FOREACH(client_link, &main_chan->base.clients) {
> + MainChannelClient *mcc;
> +
> + mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link);
> + if (mcc->mig_wait_connect) {
> + red_printf("client %p cancel wait connect", mcc->base.client);
> + mcc->mig_wait_connect = FALSE;
> + mcc->mig_connect_ok = FALSE;
> + }
> + }
> + main_chan->num_clients_mig_wait = 0;
> +}
> diff --git a/server/main_channel.h b/server/main_channel.h
> index 2ae05e8..f3702e7 100644
> --- a/server/main_channel.h
> +++ b/server/main_channel.h
> @@ -57,6 +57,8 @@ struct MainMigrateData {
> typedef struct MainChannel {
> RedChannel base;
> uint8_t recv_buf[RECEIVE_BUF_SIZE];
> + RedsMigSpice mig_target; // TODO: add refs and release (afrer all clients completed migration in one way or the other?)
> + int num_clients_mig_wait;
> } MainChannel;
>
>
> @@ -80,9 +82,6 @@ void main_channel_push_init(MainChannelClient *mcc, int connection_id, int displ
> int current_mouse_mode, int is_client_mouse_allowed, int multi_media_time,
> int ram_hint);
> void main_channel_push_notify(MainChannel *main_chan, uint8_t *mess, const int mess_len);
> -// TODO: consider exporting RedsMigSpice from reds.c
> -void main_channel_push_migrate_begin(MainChannel *main_chan, int port, int sport, char *host,
> - uint16_t cert_pub_key_type, uint32_t cert_pub_key_len, uint8_t *cert_pub_key);
> void main_channel_push_migrate(MainChannel *main_chan);
> void main_channel_push_migrate_switch(MainChannel *main_chan);
> void main_channel_push_migrate_cancel(MainChannel *main_chan);
> @@ -94,5 +93,12 @@ uint32_t main_channel_client_get_link_id(MainChannelClient *mcc);
> int main_channel_client_is_low_bandwidth(MainChannelClient *mcc);
> uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc);
> int main_channel_is_connected(MainChannel *main_chan);
> +RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc);
>
> +/* semi seamless migration */
> +
> +/* returns the number of clients that we are waiting for their connection */
> +int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target);
> +void main_channel_migrate_cancel_wait(MainChannel *main_chan);
> +void main_channel_migrate_complete(MainChannel *main_chan, int success);
> #endif
> diff --git a/server/red_channel.h b/server/red_channel.h
> index d044253..e30401c 100644
> --- a/server/red_channel.h
> +++ b/server/red_channel.h
> @@ -450,6 +450,7 @@ struct RedClient {
> pthread_t thread_id;
>
> int disconnecting;
> +
> };
>
> RedClient *red_client_new(void);
> @@ -457,7 +458,6 @@ MainChannelClient *red_client_get_main(RedClient *client);
> // main should be set once before all the other channels are created
> void red_client_set_main(RedClient *client, MainChannelClient *mcc);
>
> -
> void red_client_migrate(RedClient *client);
> // disconnects all the client's channels (should be called from the client's thread)
> void red_client_destroy(RedClient *client);
> diff --git a/server/reds.c b/server/reds.c
> index 40b54bb..009ad21 100644
> --- a/server/reds.c
> +++ b/server/reds.c
> @@ -190,8 +190,6 @@ typedef struct RedsStatValue {
>
> #endif
>
> -typedef struct RedsMigSpice RedsMigSpice;
> -
> typedef struct RedsState {
> int listen_socket;
> int secure_listen_socket;
> @@ -294,6 +292,7 @@ struct ChannelSecurityOptions {
> ChannelSecurityOptions *next;
> };
>
> +static void migrate_timeout(void *opaque);
>
> static ChannelSecurityOptions *channels_security = NULL;
> static int default_channel_security =
> @@ -537,6 +536,12 @@ static RedChannel *reds_find_channel(uint32_t type, uint32_t id)
> static void reds_mig_cleanup(void)
> {
> if (reds->mig_inprogress) {
> + if (reds->mig_wait_connect) {
> + SpiceMigrateInterface *sif;
> + ASSERT(migration_interface);
> + sif = SPICE_CONTAINEROF(migration_interface->base.sif, SpiceMigrateInterface, base);
> + sif->migrate_connect_complete(migration_interface);
> + }
> reds->mig_inprogress = FALSE;
> reds->mig_wait_connect = FALSE;
> reds->mig_wait_disconnect = FALSE;
> @@ -1059,13 +1064,6 @@ void reds_on_main_migrate_connected(void)
> }
> }
>
> -void reds_on_main_migrate_connect_error(void)
> -{
> - if (reds->mig_wait_connect) {
> - reds_mig_cleanup();
> - }
> -}
> -
> void reds_on_main_mouse_mode_request(void *message, size_t size)
> {
> switch (((SpiceMsgcMainMouseModeRequest *)message)->mode) {
> @@ -2975,18 +2973,6 @@ static void set_one_channel_security(int id, uint32_t security)
>
> #define REDS_SAVE_VERSION 1
>
> -struct RedsMigSpice {
> - char pub_key[SPICE_TICKET_PUBKEY_BYTES];
> - uint32_t mig_key;
> - char *host;
> - char *cert_subject;
> - int port;
> - int sport;
> - uint16_t cert_pub_key_type;
> - uint32_t cert_pub_key_len;
> - uint8_t* cert_pub_key;
> -};
> -
> typedef struct RedsMigSpiceMessage {
> uint32_t connection_id;
> } RedsMigSpiceMessage;
> @@ -3006,24 +2992,12 @@ void reds_mig_release(void)
> }
> }
>
> -static void reds_mig_continue(void)
> -{
> - RedsMigSpice *s = reds->mig_spice;
> -
> - red_printf("");
> - main_channel_push_migrate_begin(reds->main_channel, s->port, s->sport,
> - s->host, s->cert_pub_key_type, s->cert_pub_key_len, s->cert_pub_key);
> -
> - reds_mig_release();
> -
> - reds->mig_wait_connect = TRUE;
> - core->timer_start(reds->mig_timer, MIGRATE_TIMEOUT);
> -}
> -
> static void reds_mig_started(void)
> {
> red_printf("");
> + ASSERT(reds->mig_spice);
>
> + reds->mig_wait_connect = TRUE;
> reds->mig_inprogress = TRUE;
>
> if (reds->listen_watch != NULL) {
> @@ -3033,24 +3007,7 @@ static void reds_mig_started(void)
> if (reds->secure_listen_watch != NULL) {
> core->watch_update_mask(reds->secure_listen_watch, 0);
> }
> -
> - if (!reds_main_channel_connected()) {
> - red_printf("not connected to peer");
> - goto error;
> - }
> -
> - if ((SPICE_VERSION_MAJOR == 1) && (reds->peer_minor_version < 2)) {
> - red_printf("minor version mismatch client %u server %u",
> - reds->peer_minor_version, SPICE_VERSION_MINOR);
> - goto error;
> - }
> -
> - reds_mig_continue();
> - return;
> -
> -error:
> - reds_mig_release();
> - reds_mig_disconnect();
> + core->timer_start(reds->mig_timer, MIGRATE_TIMEOUT);
> }
>
> static void reds_mig_finished(int completed)
> @@ -3126,11 +3083,16 @@ void reds_fill_mig_switch(SpiceMsgMainMigrationSwitchHost *migrate)
> }
> }
>
> -static void migrate_timout(void *opaque)
> +static void migrate_timeout(void *opaque)
> {
> red_printf("");
> ASSERT(reds->mig_wait_connect || reds->mig_wait_disconnect);
> - reds_mig_disconnect();
> + if (reds->mig_wait_connect) {
> + main_channel_migrate_cancel_wait(reds->main_channel);
> + reds_mig_cleanup();
> + } else {
> + reds_mig_disconnect();
> + }
> }
>
> uint32_t reds_get_mm_time(void)
> @@ -3490,7 +3452,7 @@ static int do_spice_init(SpiceCoreInterface *core_interface)
> reds->num_clients = 0;
> ring_init(&reds->channels);
>
> - if (!(reds->mig_timer = core->timer_add(migrate_timout, NULL))) {
> + if (!(reds->mig_timer = core->timer_add(migrate_timeout, NULL))) {
> red_error("migration timer create failed");
> }
> if (!(reds->vdi_port_write_timer = core->timer_add(vdi_port_write_retry, NULL)))
> @@ -3873,19 +3835,55 @@ SPICE_GNUC_VISIBLE int spice_server_set_agent_copypaste(SpiceServer *s, int enab
> return 0;
> }
>
> +/* returns FALSE if info is invalid */
> +static int reds_set_migration_dest_info(const char* dest,
> + int port, int secure_port,
> + const char* cert_subject)
> +{
> + RedsMigSpice *spice_migration = NULL;
> +
> + reds_mig_release();
> + if ((port == -1 && secure_port == -1) || !dest) {
> + return FALSE;
> + }
> +
> + spice_migration = spice_new0(RedsMigSpice, 1);
> + spice_migration->port = port;
> + spice_migration->sport = secure_port;
> + spice_migration->host = strdup(dest);
> + if (cert_subject) {
> + spice_migration->cert_subject = strdup(cert_subject);
> + }
> +
> + reds->mig_spice = spice_migration;
> +
> + return TRUE;
> +}
> +
> /* semi-seamless client migration */
> SPICE_GNUC_VISIBLE int spice_server_migrate_connect(SpiceServer *s, const char* dest,
> int port, int secure_port,
> const char* cert_subject)
> {
> SpiceMigrateInterface *sif;
> +
> red_printf("");
> ASSERT(migration_interface);
> ASSERT(reds == s);
>
> - red_printf("not implemented yet");
> sif = SPICE_CONTAINEROF(migration_interface->base.sif, SpiceMigrateInterface, base);
> - sif->migrate_connect_complete(migration_interface);
> +
> + if (!reds_set_migration_dest_info(dest, port, secure_port, cert_subject)) {
> + sif->migrate_connect_complete(migration_interface);
> + return -1;
> + }
> +
> + if (main_channel_migrate_connect(reds->main_channel, reds->mig_spice)) {
> + reds->mig_wait_connect = TRUE;
> + reds_mig_started();
> + } else {
> + sif->migrate_connect_complete(migration_interface);
> + }
>
> return 0;
> }
> @@ -3894,27 +3892,16 @@ SPICE_GNUC_VISIBLE int spice_server_migrate_info(SpiceServer *s, const char* des
> int port, int secure_port,
> const char* cert_subject)
> {
> - RedsMigSpice *spice_migration = NULL;
> -
> + ASSERT(!migration_interface);
> ASSERT(reds == s);
>
> - if ((port == -1 && secure_port == -1) || !dest)
> + if (!reds_set_migration_dest_info(dest, port, secure_port, cert_subject)) {
> return -1;
> -
> - spice_migration = spice_new0(RedsMigSpice, 1);
> - spice_migration->port = port;
> - spice_migration->sport = secure_port;
> - spice_migration->host = strdup(dest);
> - if (cert_subject) {
> - spice_migration->cert_subject = strdup(cert_subject);
> }
>
> - reds_mig_release();
> - reds->mig_spice = spice_migration;
> return 0;
> }
>
> -/* interface for seamless migration */
> SPICE_GNUC_VISIBLE int spice_server_migrate_start(SpiceServer *s)
> {
> ASSERT(reds == s);
> diff --git a/server/reds.h b/server/reds.h
> index 7720b30..13fec42 100644
> --- a/server/reds.h
> +++ b/server/reds.h
> @@ -99,6 +99,13 @@ struct SpiceMigrateState {
> int dummy;
> };
>
> +typedef struct RedsMigSpice {
> + char *host;
> + char *cert_subject;
> + int port;
> + int sport;
> +} RedsMigSpice;
> +
> ssize_t reds_stream_read(RedsStream *s, void *buf, size_t nbyte);
> ssize_t reds_stream_write(RedsStream *s, const void *buf, size_t nbyte);
> ssize_t reds_stream_writev(RedsStream *s, const struct iovec *iov, int iovcnt);
> @@ -134,11 +141,12 @@ int reds_num_of_clients(void);
> void reds_update_stat_value(uint32_t value);
> #endif
>
> -// callbacks from main channel messages
> +/* callbacks from main channel messages */
> +
> void reds_on_main_agent_start(void);
> void reds_on_main_agent_data(MainChannelClient *mcc, void *message, size_t size);
> -void reds_on_main_migrate_connected(void);
> -void reds_on_main_migrate_connect_error(void);
> +void reds_on_main_migrate_connected(void); //should be called when all the clients
> + // are connected to the target
> void reds_on_main_receive_migrate_data(MainMigrateData *data, uint8_t *end);
> void reds_on_main_mouse_mode_request(void *message, size_t size);
>
> diff --git a/spice.proto b/spice.proto
> index abf3ec3..78c1fad 100644
> --- a/spice.proto
> +++ b/spice.proto
> @@ -167,9 +167,8 @@ channel MainChannel : BaseChannel {
> uint16 sport;
> uint32 host_size;
> uint8 *host_data[host_size] @zero_terminated @marshall @nonnull;
> - pubkey_type pub_key_type;
> - uint32 pub_key_size;
> - uint8 *pub_key_data[pub_key_size] @zero_terminated @marshall @nonnull;
> + uint32 cert_subject_size;
> + uint8 *cert_subject_data[cert_subject_size] @zero_terminated @marshall;
> } @ctype(SpiceMsgMainMigrationBegin) migrate_begin = 101;
>
> Empty migrate_cancel;
> --
> 1.7.6.4
>
More information about the Spice-devel
mailing list