/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* Copyright (C) 2009 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/generated_server_marshallers.h" #include "common/messages.h" #include "common/ring.h" #include "demarshallers.h" #include "main-channel.h" #include "red-channel.h" #include "red-common.h" #include "reds.h" #include "migration-protocol.h" #include "main-dispatcher.h" #include "utils.h" #define ZERO_BUF_SIZE 4096 #define NET_TEST_WARMUP_BYTES 0 #define NET_TEST_BYTES (1024 * 250) #define PING_INTERVAL (MSEC_PER_SEC * 10) #define CLIENT_CONNECTIVITY_TIMEOUT (MSEC_PER_SEC * 30) static const uint8_t zero_page[ZERO_BUF_SIZE] = {0}; enum { PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST = PIPE_ITEM_TYPE_CHANNEL_BASE, PIPE_ITEM_TYPE_MAIN_PING, PIPE_ITEM_TYPE_MAIN_MOUSE_MODE, PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED, PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN, PIPE_ITEM_TYPE_MAIN_AGENT_DATA, PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA, PIPE_ITEM_TYPE_MAIN_INIT, PIPE_ITEM_TYPE_MAIN_NOTIFY, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST, PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME, PIPE_ITEM_TYPE_MAIN_NAME, PIPE_ITEM_TYPE_MAIN_UUID, PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS, }; typedef struct RefsPipeItem { PipeItem base; int *refs; } RefsPipeItem; typedef struct PingPipeItem { PipeItem base; int size; } PingPipeItem; typedef struct MouseModePipeItem { PipeItem base; int current_mode; int is_client_mouse_allowed; } MouseModePipeItem; typedef struct TokensPipeItem { PipeItem base; int tokens; } TokensPipeItem; typedef struct AgentDataPipeItem { PipeItem base; uint8_t* data; size_t len; spice_marshaller_item_free_func free_data; void *opaque; } AgentDataPipeItem; typedef struct InitPipeItem { PipeItem base; int connection_id; int display_channels_hint; int current_mouse_mode; int is_client_mouse_allowed; int multi_media_time; int ram_hint; } InitPipeItem; typedef struct NamePipeItem { PipeItem base; SpiceMsgMainName msg; } NamePipeItem; typedef struct UuidPipeItem { PipeItem base; SpiceMsgMainUuid msg; } UuidPipeItem; typedef struct NotifyPipeItem { PipeItem base; char *msg; } NotifyPipeItem; typedef struct MultiMediaTimePipeItem { PipeItem base; int time; } MultiMediaTimePipeItem; struct MainChannelClient { RedChannelClient base; uint32_t connection_id; uint32_t ping_id; uint32_t net_test_id; int net_test_stage; uint64_t latency; uint64_t bitrate_per_sec; #ifdef RED_STATISTICS SpiceTimer *ping_timer; int ping_interval; #endif int mig_wait_connect; int mig_connect_ok; int mig_wait_prev_complete; int mig_wait_prev_try_seamless; int init_sent; int seamless_mig_dst; }; enum NetTestStage { NET_TEST_STAGE_INVALID, NET_TEST_STAGE_WARMUP, NET_TEST_STAGE_LATENCY, NET_TEST_STAGE_RATE, NET_TEST_STAGE_COMPLETE, }; static void main_channel_release_pipe_item(RedChannelClient *rcc, PipeItem *base, int item_pushed); int main_channel_is_connected(MainChannel *main_chan) { return red_channel_is_connected(&main_chan->base); } /* * When the main channel is disconnected, disconnect the entire client. */ static void main_channel_client_on_disconnect(RedChannelClient *rcc) { RedsState *reds = red_channel_get_server(rcc->channel); spice_printerr("rcc=%p", rcc); main_dispatcher_client_disconnect(reds_get_main_dispatcher(reds), rcc->client); } RedClient *main_channel_get_client_by_link_id(MainChannel *main_chan, uint32_t connection_id) { RingItem *link; MainChannelClient *mcc; RING_FOREACH(link, &main_chan->base.clients) { mcc = SPICE_CONTAINEROF(link, MainChannelClient, base.channel_link); if (mcc->connection_id == connection_id) { return mcc->base.client; } } return NULL; } static int main_channel_client_push_ping(MainChannelClient *mcc, int size); void main_channel_client_start_net_test(MainChannelClient *mcc, int test_rate) { if (!mcc || mcc->net_test_id) { return; } if (test_rate) { if (main_channel_client_push_ping(mcc, NET_TEST_WARMUP_BYTES) && main_channel_client_push_ping(mcc, 0) && main_channel_client_push_ping(mcc, NET_TEST_BYTES)) { mcc->net_test_id = mcc->ping_id - 2; mcc->net_test_stage = NET_TEST_STAGE_WARMUP; } } else { red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT); } } typedef struct MainMouseModeItemInfo { int current_mode; int is_client_mouse_allowed; } MainMouseModeItemInfo; static PipeItem *main_mouse_mode_item_new(RedChannelClient *rcc, void *data, int num) { MouseModePipeItem *item = spice_malloc(sizeof(MouseModePipeItem)); MainMouseModeItemInfo *info = data; pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_MOUSE_MODE); item->current_mode = info->current_mode; item->is_client_mouse_allowed = info->is_client_mouse_allowed; return &item->base; } static PipeItem *main_ping_item_new(MainChannelClient *mcc, int size) { PingPipeItem *item = spice_malloc(sizeof(PingPipeItem)); pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_PING); item->size = size; return &item->base; } static PipeItem *main_agent_tokens_item_new(RedChannelClient *rcc, uint32_t num_tokens) { TokensPipeItem *item = spice_malloc(sizeof(TokensPipeItem)); pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN); item->tokens = num_tokens; return &item->base; } static PipeItem *main_agent_data_item_new(RedChannelClient *rcc, uint8_t* data, size_t len, spice_marshaller_item_free_func free_data, void *opaque) { AgentDataPipeItem *item = spice_malloc(sizeof(AgentDataPipeItem)); pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_AGENT_DATA); item->data = data; item->len = len; item->free_data = free_data; item->opaque = opaque; return &item->base; } static PipeItem *main_init_item_new(MainChannelClient *mcc, int connection_id, int display_channels_hint, int current_mouse_mode, int is_client_mouse_allowed, int multi_media_time, int ram_hint) { InitPipeItem *item = spice_malloc(sizeof(InitPipeItem)); pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_INIT); item->connection_id = connection_id; item->display_channels_hint = display_channels_hint; item->current_mouse_mode = current_mouse_mode; item->is_client_mouse_allowed = is_client_mouse_allowed; item->multi_media_time = multi_media_time; item->ram_hint = ram_hint; return &item->base; } static PipeItem *main_name_item_new(MainChannelClient *mcc, const char *name) { NamePipeItem *item = spice_malloc(sizeof(NamePipeItem) + strlen(name) + 1); pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_NAME); item->msg.name_len = strlen(name) + 1; memcpy(&item->msg.name, name, item->msg.name_len); return &item->base; } static PipeItem *main_uuid_item_new(MainChannelClient *mcc, const uint8_t uuid[16]) { UuidPipeItem *item = spice_malloc(sizeof(UuidPipeItem)); pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_UUID); memcpy(item->msg.uuid, uuid, sizeof(item->msg.uuid)); return &item->base; } static PipeItem *main_notify_item_new(RedChannelClient *rcc, void *data, int num) { NotifyPipeItem *item = spice_malloc(sizeof(NotifyPipeItem)); const char *msg = data; pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_NOTIFY); item->msg = spice_strdup(msg); return &item->base; } static PipeItem *main_multi_media_time_item_new( RedChannelClient *rcc, void *data, int num) { MultiMediaTimePipeItem *item, *info = data; item = spice_malloc(sizeof(MultiMediaTimePipeItem)); pipe_item_init(&item->base, PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME); item->time = info->time; return &item->base; } static void main_channel_push_channels(MainChannelClient *mcc) { if (red_client_during_migrate_at_target(mcc->base.client)) { spice_printerr("warning: ignoring unexpected SPICE_MSGC_MAIN_ATTACH_CHANNELS" "during migration"); return; } red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST); } static void main_channel_marshall_channels(RedChannelClient *rcc, SpiceMarshaller *m, PipeItem *item) { SpiceMsgChannels* channels_info; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_CHANNELS_LIST, item); channels_info = (SpiceMsgChannels *)spice_malloc(sizeof(SpiceMsgChannels) + reds_get_n_channels(rcc->channel->reds) * sizeof(SpiceChannelId)); reds_fill_channels(rcc->channel->reds, channels_info); spice_marshall_msg_main_channels_list(m, channels_info); free(channels_info); } int main_channel_client_push_ping(MainChannelClient *mcc, int size) { PipeItem *item; if (mcc == NULL) { return FALSE; } item = main_ping_item_new(mcc, size); red_channel_client_pipe_add_push(&mcc->base, item); return TRUE; } static void main_channel_marshall_ping(RedChannelClient *rcc, SpiceMarshaller *m, PingPipeItem *item) { MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); SpiceMsgPing ping; int size_left = item->size; red_channel_client_init_send_data(rcc, SPICE_MSG_PING, &item->base); ping.id = ++(mcc->ping_id); ping.timestamp = g_get_monotonic_time(); spice_marshall_msg_ping(m, &ping); while (size_left > 0) { int now = MIN(ZERO_BUF_SIZE, size_left); size_left -= now; spice_marshaller_add_ref(m, zero_page, now); } } void main_channel_push_mouse_mode(MainChannel *main_chan, int current_mode, int is_client_mouse_allowed) { MainMouseModeItemInfo info = { .current_mode=current_mode, .is_client_mouse_allowed=is_client_mouse_allowed, }; red_channel_pipes_new_add_push(&main_chan->base, main_mouse_mode_item_new, &info); } static void main_channel_marshall_mouse_mode(RedChannelClient *rcc, SpiceMarshaller *m, MouseModePipeItem *item) { SpiceMsgMainMouseMode mouse_mode; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MOUSE_MODE, &item->base); mouse_mode.supported_modes = SPICE_MOUSE_MODE_SERVER; if (item->is_client_mouse_allowed) { mouse_mode.supported_modes |= SPICE_MOUSE_MODE_CLIENT; } mouse_mode.current_mode = item->current_mode; spice_marshall_msg_main_mouse_mode(m, &mouse_mode); } void main_channel_push_agent_connected(MainChannel *main_chan) { if (red_channel_test_remote_cap(&main_chan->base, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS)) { red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS); } else { red_channel_pipes_add_empty_msg(&main_chan->base, SPICE_MSG_MAIN_AGENT_CONNECTED); } } static void main_channel_marshall_agent_connected(SpiceMarshaller *m, RedChannelClient *rcc, PipeItem *item) { SpiceMsgMainAgentConnectedTokens connected; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS, item); connected.num_tokens = REDS_AGENT_WINDOW_SIZE; spice_marshall_msg_main_agent_connected_tokens(m, &connected); } void main_channel_push_agent_disconnected(MainChannel *main_chan) { red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED); } static void main_channel_marshall_agent_disconnected(RedChannelClient *rcc, SpiceMarshaller *m, PipeItem *item) { SpiceMsgMainAgentDisconnect disconnect; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DISCONNECTED, item); disconnect.error_code = SPICE_LINK_ERR_OK; spice_marshall_msg_main_agent_disconnected(m, &disconnect); } void main_channel_client_push_agent_tokens(MainChannelClient *mcc, uint32_t num_tokens) { PipeItem *item = main_agent_tokens_item_new(&mcc->base, num_tokens); red_channel_client_pipe_add_push(&mcc->base, item); } static void main_channel_marshall_tokens(RedChannelClient *rcc, SpiceMarshaller *m, TokensPipeItem *item) { SpiceMsgMainAgentTokens tokens; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_TOKEN, &item->base); tokens.num_tokens = item->tokens; spice_marshall_msg_main_agent_token(m, &tokens); } void main_channel_client_push_agent_data(MainChannelClient *mcc, uint8_t* data, size_t len, spice_marshaller_item_free_func free_data, void *opaque) { PipeItem *item; item = main_agent_data_item_new(&mcc->base, data, len, free_data, opaque); red_channel_client_pipe_add_push(&mcc->base, item); } static void main_channel_marshall_agent_data(RedChannelClient *rcc, SpiceMarshaller *m, AgentDataPipeItem *item) { red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_AGENT_DATA, &item->base); spice_marshaller_add_ref(m, item->data, item->len); } static void main_channel_push_migrate_data_item(MainChannel *main_chan) { red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA); } static void main_channel_marshall_migrate_data_item(RedChannelClient *rcc, SpiceMarshaller *m, PipeItem *item) { red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, item); reds_marshall_migrate_data(rcc->channel->reds, m); // TODO: from reds split. ugly separation. } static int main_channel_handle_migrate_data(RedChannelClient *rcc, uint32_t size, void *message) { MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); SpiceMigrateDataHeader *header = (SpiceMigrateDataHeader *)message; /* not supported with multi-clients */ spice_assert(rcc->channel->clients_num == 1); if (size < sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataMain)) { spice_printerr("bad message size %u", size); return FALSE; } if (!migration_protocol_validate_header(header, SPICE_MIGRATE_DATA_MAIN_MAGIC, SPICE_MIGRATE_DATA_MAIN_VERSION)) { spice_error("bad header"); return FALSE; } return reds_handle_migrate_data(rcc->channel->reds, mcc, (SpiceMigrateDataMain *)(header + 1), size); } void main_channel_push_init(MainChannelClient *mcc, int display_channels_hint, int current_mouse_mode, int is_client_mouse_allowed, int multi_media_time, int ram_hint) { PipeItem *item; item = main_init_item_new(mcc, mcc->connection_id, display_channels_hint, current_mouse_mode, is_client_mouse_allowed, multi_media_time, ram_hint); red_channel_client_pipe_add_push(&mcc->base, item); } static void main_channel_marshall_init(RedChannelClient *rcc, SpiceMarshaller *m, InitPipeItem *item) { SpiceMsgMainInit init; // TODO - remove this copy, make InitPipeItem reuse SpiceMsgMainInit red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_INIT, &item->base); init.session_id = item->connection_id; init.display_channels_hint = item->display_channels_hint; init.current_mouse_mode = item->current_mouse_mode; init.supported_mouse_modes = SPICE_MOUSE_MODE_SERVER; if (item->is_client_mouse_allowed) { init.supported_mouse_modes |= SPICE_MOUSE_MODE_CLIENT; } init.agent_connected = reds_has_vdagent(rcc->channel->reds); init.agent_tokens = REDS_AGENT_WINDOW_SIZE; init.multi_media_time = item->multi_media_time; init.ram_hint = item->ram_hint; spice_marshall_msg_main_init(m, &init); } void main_channel_push_name(MainChannelClient *mcc, const char *name) { PipeItem *item; if (!red_channel_client_test_remote_cap(&mcc->base, SPICE_MAIN_CAP_NAME_AND_UUID)) return; item = main_name_item_new(mcc, name); red_channel_client_pipe_add_push(&mcc->base, item); } void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]) { PipeItem *item; if (!red_channel_client_test_remote_cap(&mcc->base, SPICE_MAIN_CAP_NAME_AND_UUID)) return; item = main_uuid_item_new(mcc, uuid); red_channel_client_pipe_add_push(&mcc->base, item); } void main_channel_client_push_notify(MainChannelClient *mcc, const char *msg) { PipeItem *item = main_notify_item_new(&mcc->base, (void *)msg, 1); red_channel_client_pipe_add_push(&mcc->base, item); } static void main_channel_marshall_notify(RedChannelClient *rcc, SpiceMarshaller *m, NotifyPipeItem *item) { SpiceMsgNotify notify; red_channel_client_init_send_data(rcc, SPICE_MSG_NOTIFY, &item->base); notify.time_stamp = spice_get_monotonic_time_ns(); // TODO - move to main_new_notify_item notify.severity = SPICE_NOTIFY_SEVERITY_WARN; notify.visibilty = SPICE_NOTIFY_VISIBILITY_HIGH; notify.what = SPICE_WARN_GENERAL; notify.message_len = strlen(item->msg); spice_marshall_msg_notify(m, ¬ify); spice_marshaller_add(m, (uint8_t *)item->msg, notify.message_len + 1); } static void main_channel_fill_migrate_dst_info(MainChannel *main_channel, SpiceMigrationDstInfo *dst_info) { RedsMigSpice *mig_dst = &main_channel->mig_target; dst_info->port = mig_dst->port; dst_info->sport = mig_dst->sport; dst_info->host_size = strlen(mig_dst->host) + 1; dst_info->host_data = (uint8_t *)mig_dst->host; if (mig_dst->cert_subject) { dst_info->cert_subject_size = strlen(mig_dst->cert_subject) + 1; dst_info->cert_subject_data = (uint8_t *)mig_dst->cert_subject; } else { dst_info->cert_subject_size = 0; dst_info->cert_subject_data = NULL; } } static void main_channel_marshall_migrate_begin(SpiceMarshaller *m, RedChannelClient *rcc, PipeItem *item) { SpiceMsgMainMigrationBegin migrate; MainChannel *main_ch; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN, item); main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); main_channel_fill_migrate_dst_info(main_ch, &migrate.dst_info); spice_marshall_msg_main_migrate_begin(m, &migrate); } static void main_channel_marshall_migrate_begin_seamless(SpiceMarshaller *m, RedChannelClient *rcc, PipeItem *item) { SpiceMsgMainMigrateBeginSeamless migrate_seamless; MainChannel *main_ch; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS, item); main_ch = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); main_channel_fill_migrate_dst_info(main_ch, &migrate_seamless.dst_info); migrate_seamless.src_mig_version = SPICE_MIGRATION_PROTOCOL_VERSION; spice_marshall_msg_main_migrate_begin_seamless(m, &migrate_seamless); } void main_channel_push_multi_media_time(MainChannel *main_chan, int time) { MultiMediaTimePipeItem info = { .time = time, }; red_channel_pipes_new_add_push(&main_chan->base, main_multi_media_time_item_new, &info); } static void main_channel_fill_mig_target(MainChannel *main_channel, RedsMigSpice *mig_target) { spice_assert(mig_target); free(main_channel->mig_target.host); main_channel->mig_target.host = spice_strdup(mig_target->host); free(main_channel->mig_target.cert_subject); if (mig_target->cert_subject) { main_channel->mig_target.cert_subject = spice_strdup(mig_target->cert_subject); } else { main_channel->mig_target.cert_subject = NULL; } main_channel->mig_target.port = mig_target->port; main_channel->mig_target.sport = mig_target->sport; } void main_channel_migrate_switch(MainChannel *main_chan, RedsMigSpice *mig_target) { main_channel_fill_mig_target(main_chan, mig_target); red_channel_pipes_add_type(&main_chan->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST); } static void main_channel_marshall_migrate_switch(SpiceMarshaller *m, RedChannelClient *rcc, PipeItem *item) { SpiceMsgMainMigrationSwitchHost migrate; MainChannel *main_ch; spice_printerr(""); red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST, item); 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_switch_host(m, &migrate); } static void main_channel_marshall_multi_media_time(RedChannelClient *rcc, SpiceMarshaller *m, MultiMediaTimePipeItem *item) { SpiceMsgMainMultiMediaTime time_mes; red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_MULTI_MEDIA_TIME, &item->base); time_mes.time = item->time; spice_marshall_msg_main_multi_media_time(m, &time_mes); } static void main_channel_send_item(RedChannelClient *rcc, PipeItem *base) { MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); SpiceMarshaller *m = red_channel_client_get_marshaller(rcc); /* In semi-seamless migration (dest side), the connection is started from scratch, and * we ignore any pipe item that arrives before the INIT msg is sent. * For seamless we don't send INIT, and the connection continues from the same place * it stopped on the src side. */ if (!mcc->init_sent && !mcc->seamless_mig_dst && base->type != PIPE_ITEM_TYPE_MAIN_INIT) { spice_printerr("Init msg for client %p was not sent yet " "(client is probably during semi-seamless migration). Ignoring msg type %d", rcc->client, base->type); main_channel_release_pipe_item(rcc, base, FALSE); return; } switch (base->type) { case PIPE_ITEM_TYPE_MAIN_CHANNELS_LIST: main_channel_marshall_channels(rcc, m, base); break; case PIPE_ITEM_TYPE_MAIN_PING: main_channel_marshall_ping(rcc, m, SPICE_CONTAINEROF(base, PingPipeItem, base)); break; case PIPE_ITEM_TYPE_MAIN_MOUSE_MODE: { MouseModePipeItem *item = SPICE_CONTAINEROF(base, MouseModePipeItem, base); main_channel_marshall_mouse_mode(rcc, m, item); break; } case PIPE_ITEM_TYPE_MAIN_AGENT_DISCONNECTED: main_channel_marshall_agent_disconnected(rcc, m, base); break; case PIPE_ITEM_TYPE_MAIN_AGENT_TOKEN: main_channel_marshall_tokens(rcc, m, SPICE_CONTAINEROF(base, TokensPipeItem, base)); break; case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: main_channel_marshall_agent_data(rcc, m, SPICE_CONTAINEROF(base, AgentDataPipeItem, base)); break; case PIPE_ITEM_TYPE_MAIN_MIGRATE_DATA: main_channel_marshall_migrate_data_item(rcc, m, base); break; case PIPE_ITEM_TYPE_MAIN_INIT: mcc->init_sent = TRUE; main_channel_marshall_init(rcc, m, SPICE_CONTAINEROF(base, InitPipeItem, base)); break; case PIPE_ITEM_TYPE_MAIN_NOTIFY: main_channel_marshall_notify(rcc, m, SPICE_CONTAINEROF(base, NotifyPipeItem, base)); break; case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN: main_channel_marshall_migrate_begin(m, rcc, base); break; case PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS: main_channel_marshall_migrate_begin_seamless(m, rcc, base); break; case PIPE_ITEM_TYPE_MAIN_MULTI_MEDIA_TIME: main_channel_marshall_multi_media_time(rcc, m, SPICE_CONTAINEROF(base, MultiMediaTimePipeItem, base)); break; case PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST: main_channel_marshall_migrate_switch(m, rcc, base); break; case PIPE_ITEM_TYPE_MAIN_NAME: red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_NAME, base); spice_marshall_msg_main_name(m, &SPICE_CONTAINEROF(base, NamePipeItem, base)->msg); break; case PIPE_ITEM_TYPE_MAIN_UUID: red_channel_client_init_send_data(rcc, SPICE_MSG_MAIN_UUID, base); spice_marshall_msg_main_uuid(m, &SPICE_CONTAINEROF(base, UuidPipeItem, base)->msg); break; case PIPE_ITEM_TYPE_MAIN_AGENT_CONNECTED_TOKENS: main_channel_marshall_agent_connected(m, rcc, base); break; default: break; }; red_channel_client_begin_send_message(rcc); } static void main_channel_release_pipe_item(RedChannelClient *rcc, PipeItem *base, int item_pushed) { switch (base->type) { case PIPE_ITEM_TYPE_MAIN_AGENT_DATA: { AgentDataPipeItem *data = (AgentDataPipeItem *)base; data->free_data(data->data, data->opaque); break; } case PIPE_ITEM_TYPE_MAIN_NOTIFY: { NotifyPipeItem *data = (NotifyPipeItem *)base; free(data->msg); break; } default: break; } free(base); } static void main_channel_client_handle_migrate_connected(MainChannelClient *mcc, int success, int seamless) { spice_printerr("client %p connected: %d seamless %d", mcc->base.client, success, seamless); 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; spice_assert(main_channel->num_clients_mig_wait); spice_assert(!seamless || main_channel->num_clients_mig_wait == 1); if (!--main_channel->num_clients_mig_wait) { reds_on_main_migrate_connected(mcc->base.channel->reds, seamless && success); } } else { if (success) { spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client); red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL); } } } void main_channel_client_handle_migrate_dst_do_seamless(MainChannelClient *mcc, uint32_t src_version) { if (reds_on_migrate_dst_set_seamless(mcc->base.channel->reds, mcc, src_version)) { mcc->seamless_mig_dst = TRUE; red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK); } else { red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK); } } void main_channel_client_handle_migrate_end(MainChannelClient *mcc) { if (!red_client_during_migrate_at_target(mcc->base.client)) { spice_printerr("unexpected SPICE_MSGC_MIGRATE_END"); return; } if (!red_channel_client_test_remote_cap(&mcc->base, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE)) { spice_printerr("unexpected SPICE_MSGC_MIGRATE_END, " "client does not support semi-seamless migration"); return; } red_client_semi_seamless_migrate_complete(mcc->base.client); } void main_channel_migrate_dst_complete(MainChannelClient *mcc) { if (mcc->mig_wait_prev_complete) { if (mcc->mig_wait_prev_try_seamless) { spice_assert(mcc->base.channel->clients_num == 1); red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS); } else { red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN); } mcc->mig_wait_connect = TRUE; mcc->mig_wait_prev_complete = FALSE; } } 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); MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); switch (type) { case SPICE_MSGC_MAIN_AGENT_START: { SpiceMsgcMainAgentStart *tokens; spice_printerr("agent start"); if (!main_chan) { return FALSE; } tokens = (SpiceMsgcMainAgentStart *)message; reds_on_main_agent_start(rcc->channel->reds, mcc, tokens->num_tokens); break; } case SPICE_MSGC_MAIN_AGENT_DATA: { reds_on_main_agent_data(rcc->channel->reds, mcc, message, size); break; } case SPICE_MSGC_MAIN_AGENT_TOKEN: { SpiceMsgcMainAgentTokens *tokens; tokens = (SpiceMsgcMainAgentTokens *)message; reds_on_main_agent_tokens(rcc->channel->reds, mcc, tokens->num_tokens); break; } case SPICE_MSGC_MAIN_ATTACH_CHANNELS: main_channel_push_channels(mcc); break; case SPICE_MSGC_MAIN_MIGRATE_CONNECTED: main_channel_client_handle_migrate_connected(mcc, TRUE /* success */, FALSE /* seamless */); break; case SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS: main_channel_client_handle_migrate_connected(mcc, TRUE /* success */, TRUE /* seamless */); break; case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR: main_channel_client_handle_migrate_connected(mcc, FALSE, FALSE); break; case SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS: main_channel_client_handle_migrate_dst_do_seamless(mcc, ((SpiceMsgcMainMigrateDstDoSeamless *)message)->src_version); break; case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST: reds_on_main_mouse_mode_request(rcc->channel->reds, message, size); break; case SPICE_MSGC_PONG: { SpiceMsgPing *ping = (SpiceMsgPing *)message; uint64_t roundtrip; roundtrip = g_get_monotonic_time() - ping->timestamp; if (ping->id == mcc->net_test_id) { switch (mcc->net_test_stage) { case NET_TEST_STAGE_WARMUP: mcc->net_test_id++; mcc->net_test_stage = NET_TEST_STAGE_LATENCY; mcc->latency = roundtrip; break; case NET_TEST_STAGE_LATENCY: mcc->net_test_id++; mcc->net_test_stage = NET_TEST_STAGE_RATE; mcc->latency = MIN(mcc->latency, roundtrip); break; case NET_TEST_STAGE_RATE: mcc->net_test_id = 0; if (roundtrip <= mcc->latency) { // probably high load on client or server result with incorrect values spice_printerr("net test: invalid values, latency %" PRIu64 " roundtrip %" PRIu64 ". assuming high" " bandwidth", mcc->latency, roundtrip); mcc->latency = 0; mcc->net_test_stage = NET_TEST_STAGE_INVALID; red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT); break; } mcc->bitrate_per_sec = (uint64_t)(NET_TEST_BYTES * 8) * 1000000 / (roundtrip - mcc->latency); mcc->net_test_stage = NET_TEST_STAGE_COMPLETE; spice_printerr("net test: latency %f ms, bitrate %"PRIu64" bps (%f Mbps)%s", (double)mcc->latency / 1000, mcc->bitrate_per_sec, (double)mcc->bitrate_per_sec / 1024 / 1024, main_channel_client_is_low_bandwidth(mcc) ? " LOW BANDWIDTH" : ""); red_channel_client_start_connectivity_monitoring(&mcc->base, CLIENT_CONNECTIVITY_TIMEOUT); break; default: spice_printerr("invalid net test stage, ping id %d test id %d stage %d", ping->id, mcc->net_test_id, mcc->net_test_stage); mcc->net_test_stage = NET_TEST_STAGE_INVALID; } break; } else { /* * channel client monitors the connectivity using ping-pong messages */ red_channel_client_handle_message(rcc, size, type, message); } #ifdef RED_STATISTICS stat_update_value(rcc->channel->reds, roundtrip); #endif break; } case SPICE_MSGC_DISCONNECTING: break; case SPICE_MSGC_MAIN_MIGRATE_END: main_channel_client_handle_migrate_end(mcc); break; default: return red_channel_client_handle_message(rcc, size, type, message); } return TRUE; } static uint8_t *main_channel_alloc_msg_rcv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size) { MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base); MainChannelClient *mcc = SPICE_CONTAINEROF(rcc, MainChannelClient, base); if (type == SPICE_MSGC_MAIN_AGENT_DATA) { return reds_get_agent_data_buffer(rcc->channel->reds, mcc, size); } else { return main_chan->recv_buf; } } static void main_channel_release_msg_rcv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size, uint8_t *msg) { if (type == SPICE_MSGC_MAIN_AGENT_DATA) { reds_release_agent_data_buffer(rcc->channel->reds, msg); } } static int main_channel_config_socket(RedChannelClient *rcc) { return TRUE; } static void main_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item) { } static int main_channel_handle_migrate_flush_mark(RedChannelClient *rcc) { spice_debug(NULL); main_channel_push_migrate_data_item(SPICE_CONTAINEROF(rcc->channel, MainChannel, base)); return TRUE; } #ifdef RED_STATISTICS static void do_ping_client(MainChannelClient *mcc, const char *opt, int has_interval, int interval) { spice_printerr(""); if (!opt) { main_channel_client_push_ping(mcc, 0); } else if (!strcmp(opt, "on")) { if (has_interval && interval > 0) { mcc->ping_interval = interval * MSEC_PER_SEC; } reds_core_timer_start(mcc->base.channel->reds, mcc->ping_timer, mcc->ping_interval); } else if (!strcmp(opt, "off")) { reds_core_timer_cancel(mcc->base.channel->reds, mcc->ping_timer); } else { return; } } static void ping_timer_cb(void *opaque) { MainChannelClient *mcc = opaque; if (!red_channel_client_is_connected(&mcc->base)) { spice_printerr("not connected to peer, ping off"); reds_core_timer_cancel(mcc->base.channel->reds, mcc->ping_timer); return; } do_ping_client(mcc, NULL, 0, 0); reds_core_timer_start(mcc->base.channel->reds, mcc->ping_timer, mcc->ping_interval); } #endif /* RED_STATISTICS */ static MainChannelClient *main_channel_client_create(MainChannel *main_chan, RedClient *client, RedsStream *stream, uint32_t connection_id, int num_common_caps, uint32_t *common_caps, int num_caps, uint32_t *caps) { MainChannelClient *mcc = (MainChannelClient*) red_channel_client_create(sizeof(MainChannelClient), &main_chan->base, client, stream, FALSE, num_common_caps, common_caps, num_caps, caps); spice_assert(mcc != NULL); mcc->connection_id = connection_id; mcc->bitrate_per_sec = ~0; #ifdef RED_STATISTICS if (!(mcc->ping_timer = reds_core_timer_add(red_channel_get_server(&main_chan->base), ping_timer_cb, mcc))) { spice_error("ping timer create failed"); } mcc->ping_interval = PING_INTERVAL; #endif return mcc; } MainChannelClient *main_channel_link(MainChannel *channel, RedClient *client, RedsStream *stream, uint32_t connection_id, int migration, int num_common_caps, uint32_t *common_caps, int num_caps, uint32_t *caps) { MainChannelClient *mcc; spice_assert(channel); // TODO - migration - I removed it from channel creation, now put it // into usage somewhere (not an issue until we return migration to it's // former glory) spice_printerr("add main channel client"); mcc = main_channel_client_create(channel, client, stream, connection_id, num_common_caps, common_caps, num_caps, caps); return mcc; } int main_channel_getsockname(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen) { return main_chan ? getsockname(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1; } int main_channel_getpeername(MainChannel *main_chan, struct sockaddr *sa, socklen_t *salen) { return main_chan ? getpeername(red_channel_get_first_socket(&main_chan->base), sa, salen) : -1; } // TODO: ? shouldn't it disonnect all clients? or shutdown all main_channels? void main_channel_close(MainChannel *main_chan) { int socketfd; if (main_chan && (socketfd = red_channel_get_first_socket(&main_chan->base)) != -1) { close(socketfd); } } int main_channel_client_is_network_info_initialized(MainChannelClient *mcc) { return mcc->net_test_stage == NET_TEST_STAGE_COMPLETE; } int main_channel_client_is_low_bandwidth(MainChannelClient *mcc) { // TODO: configurable? return mcc->bitrate_per_sec < 10 * 1024 * 1024; } uint64_t main_channel_client_get_bitrate_per_sec(MainChannelClient *mcc) { return mcc->bitrate_per_sec; } uint64_t main_channel_client_get_roundtrip_ms(MainChannelClient *mcc) { return mcc->latency / 1000; } static void main_channel_client_migrate(RedChannelClient *rcc) { reds_on_main_channel_migrate(rcc->channel->reds, SPICE_CONTAINEROF(rcc, MainChannelClient, base)); red_channel_client_default_migrate(rcc); } MainChannel* main_channel_new(RedsState *reds) { RedChannel *channel; ChannelCbs channel_cbs = { NULL, }; ClientCbs client_cbs = {NULL, }; channel_cbs.config_socket = main_channel_config_socket; channel_cbs.on_disconnect = main_channel_client_on_disconnect; channel_cbs.send_item = main_channel_send_item; channel_cbs.hold_item = main_channel_hold_pipe_item; channel_cbs.release_item = main_channel_release_pipe_item; channel_cbs.alloc_recv_buf = main_channel_alloc_msg_rcv_buf; channel_cbs.release_recv_buf = main_channel_release_msg_rcv_buf; channel_cbs.handle_migrate_flush_mark = main_channel_handle_migrate_flush_mark; channel_cbs.handle_migrate_data = main_channel_handle_migrate_data; // TODO: set the migration flag of the channel channel = red_channel_create_parser(sizeof(MainChannel), reds, reds_get_core_interface(reds), SPICE_CHANNEL_MAIN, 0, FALSE, /* handle_acks */ spice_get_client_channel_parser(SPICE_CHANNEL_MAIN, NULL), main_channel_handle_parsed, &channel_cbs, SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER); spice_assert(channel); red_channel_set_cap(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE); red_channel_set_cap(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE); client_cbs.migrate = main_channel_client_migrate; red_channel_register_client_cbs(channel, &client_cbs, NULL); return (MainChannel *)channel; } RedChannelClient* main_channel_client_get_base(MainChannelClient* mcc) { spice_assert(mcc); return &mcc->base; } static int main_channel_connect_semi_seamless(MainChannel *main_channel) { RingItem *client_link; 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)) { if (red_client_during_migrate_at_target(mcc->base.client)) { spice_printerr("client %p: wait till previous migration completes", mcc->base.client); mcc->mig_wait_prev_complete = TRUE; mcc->mig_wait_prev_try_seamless = FALSE; } else { red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_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; } static int main_channel_connect_seamless(MainChannel *main_channel) { RingItem *client_link; spice_assert(main_channel->base.clients_num == 1); RING_FOREACH(client_link, &main_channel->base.clients) { MainChannelClient * mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link); spice_assert(red_channel_client_test_remote_cap(&mcc->base, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)); if (red_client_during_migrate_at_target(mcc->base.client)) { spice_printerr("client %p: wait till previous migration completes", mcc->base.client); mcc->mig_wait_prev_complete = TRUE; mcc->mig_wait_prev_try_seamless = TRUE; } else { red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_BEGIN_SEAMLESS); mcc->mig_wait_connect = TRUE; } mcc->mig_connect_ok = FALSE; main_channel->num_clients_mig_wait++; } return main_channel->num_clients_mig_wait; } int main_channel_migrate_connect(MainChannel *main_channel, RedsMigSpice *mig_target, int try_seamless) { main_channel_fill_mig_target(main_channel, mig_target); main_channel->num_clients_mig_wait = 0; if (!main_channel_is_connected(main_channel)) { return 0; } if (!try_seamless) { return main_channel_connect_semi_seamless(main_channel); } else { RingItem *client_item; MainChannelClient *mcc; client_item = ring_get_head(&main_channel->base.clients); mcc = SPICE_CONTAINEROF(client_item, MainChannelClient, base.channel_link); if (!red_channel_client_test_remote_cap(&mcc->base, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) { return main_channel_connect_semi_seamless(main_channel); } else { return main_channel_connect_seamless(main_channel); } } } 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) { spice_printerr("client %p cancel wait connect", mcc->base.client); mcc->mig_wait_connect = FALSE; mcc->mig_connect_ok = FALSE; } mcc->mig_wait_prev_complete = FALSE; } main_chan->num_clients_mig_wait = 0; } int main_channel_migrate_src_complete(MainChannel *main_chan, int success) { RingItem *client_link; int semi_seamless_count = 0; spice_printerr(""); if (ring_is_empty(&main_chan->base.clients)) { spice_printerr("no peer connected"); return 0; } RING_FOREACH(client_link, &main_chan->base.clients) { MainChannelClient *mcc; int semi_seamless_support; mcc = SPICE_CONTAINEROF(client_link, MainChannelClient, base.channel_link); semi_seamless_support = red_channel_client_test_remote_cap(&mcc->base, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE); if (semi_seamless_support && mcc->mig_connect_ok) { if (success) { spice_printerr("client %p MIGRATE_END", mcc->base.client); red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_END); semi_seamless_count++; } else { spice_printerr("client %p MIGRATE_CANCEL", mcc->base.client); red_channel_client_pipe_add_empty_msg(&mcc->base, SPICE_MSG_MAIN_MIGRATE_CANCEL); } } else { if (success) { spice_printerr("client %p SWITCH_HOST", mcc->base.client); red_channel_client_pipe_add_type(&mcc->base, PIPE_ITEM_TYPE_MAIN_MIGRATE_SWITCH_HOST); } } mcc->mig_connect_ok = FALSE; mcc->mig_wait_connect = FALSE; } return semi_seamless_count; }