/* coroutine context */ G_GNUC_INTERNAL void spice_channel_handle_migrate(SpiceChannel *channel, spice_msg_in *in) { spice_msg_out *out; spice_msg_in *data = NULL; SpiceMsgMigrate *mig = spice_msg_in_parsed(in); spice_channel *c = channel->priv; SPICE_DEBUG("%s: channel %s flags %u", __FUNCTION__, c->name, mig->flags); if (mig->flags & SPICE_MIGRATE_NEED_FLUSH) { /* iterate_write is blocking and flushing all pending write */ SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel); out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_FLUSH_MARK); spice_msg_out_send_internal(out); spice_msg_out_unref(out); SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel); } if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) { spice_channel_recv_msg(channel, get_msg_handler, &data); if (!data || data->header.type != SPICE_MSG_MIGRATE_DATA) { g_warning("expected SPICE_MSG_MIGRATE_DATA, got %d", data->header.type); } } spice_session_channel_migrate(c->session, channel); if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) { out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_DATA); spice_marshaller_add(out->marshaller, data->data, data->header.size); spice_msg_out_send_internal(out); spice_msg_out_unref(out); } }
/* coroutine context */ static void spice_channel_recv_link_msg(SpiceChannel *channel) { spice_channel *c = channel->priv; int rc, num_caps, i; g_return_if_fail(channel != NULL); rc = spice_channel_read(channel, (uint8_t*)c->peer_msg + c->peer_pos, c->peer_hdr.size - c->peer_pos); c->peer_pos += rc; if (c->peer_pos != c->peer_hdr.size) { g_critical("%s: %s: incomplete link reply (%d/%d)", c->name, __FUNCTION__, rc, c->peer_hdr.size); emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_LINK); return; } switch (c->peer_msg->error) { case SPICE_LINK_ERR_OK: /* nothing */ break; case SPICE_LINK_ERR_NEED_SECURED: c->tls = true; SPICE_DEBUG("%s: switching to tls", c->name); SPICE_CHANNEL_GET_CLASS(channel)->channel_disconnect(channel); spice_channel_connect(channel); return; default: g_warning("%s: %s: unhandled error %d", c->name, __FUNCTION__, c->peer_msg->error); SPICE_CHANNEL_GET_CLASS(channel)->channel_disconnect(channel); emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_LINK); return; } num_caps = c->peer_msg->num_channel_caps + c->peer_msg->num_common_caps; SPICE_DEBUG("%s: %s: %d caps", c->name, __FUNCTION__, num_caps); /* see original spice/client code: */ /* g_return_if_fail(c->peer_msg + c->peer_msg->caps_offset * sizeof(uint32_t) > c->peer_msg + c->peer_hdr.size); */ uint32_t *caps = (uint32_t *)((uint8_t *)c->peer_msg + c->peer_msg->caps_offset); g_array_set_size(c->remote_common_caps, c->peer_msg->num_common_caps); for (i = 0; i < c->peer_msg->num_common_caps; i++, caps++) { g_array_index(c->remote_common_caps, uint32_t, i) = *caps; SPICE_DEBUG("got caps %u %u", i, *caps); } g_array_set_size(c->remote_caps, c->peer_msg->num_channel_caps); for (i = 0; i < c->peer_msg->num_channel_caps; i++, caps++) { g_array_index(c->remote_caps, uint32_t, i) = *caps; SPICE_DEBUG("got caps %u %u", i, *caps); } c->state = SPICE_CHANNEL_STATE_AUTH; spice_channel_send_auth(channel); }
G_GNUC_INTERNAL void spice_channel_up(SpiceChannel *channel) { spice_channel *c = channel->priv; SPICE_DEBUG("%s: channel up, state %d", c->name, c->state); if (SPICE_CHANNEL_GET_CLASS(channel)->channel_up) SPICE_CHANNEL_GET_CLASS(channel)->channel_up(channel); }
/* coroutine context */ static gboolean spice_channel_iterate(SpiceChannel *channel) { spice_channel *c = channel->priv; GIOCondition ret; do { while (c->state == SPICE_CHANNEL_STATE_MIGRATING) { /* freeze coroutine */ coroutine_yield(NULL); g_return_val_if_fail(c->state != SPICE_CHANNEL_STATE_MIGRATING, FALSE); } if (c->has_error) { SPICE_DEBUG("channel has error, breaking loop"); return FALSE; } SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel); /*FIXME: this calling will fall in deadloop because coroutine_swap() returns NULL; * So I force reading. * */ ret = G_IO_IN;//g_io_wait_interruptable(&c->wait, c->sock, G_IO_IN); #ifdef WIN32 /* FIXME: windows gsocket is buggy, it doesn't return correct condition... */ ret = g_socket_condition_check(c->sock, G_IO_IN); #endif } while (ret == 0); /* ret == 0 means no IO condition, but woken up */ if (ret & (G_IO_ERR|G_IO_HUP)) { SPICE_DEBUG("got socket error before read(): %d", ret); c->has_error = TRUE; return FALSE; } SPICE_CHANNEL_GET_CLASS(channel)->iterate_read(channel); return TRUE; }
/* coroutine context */ G_GNUC_INTERNAL void spice_channel_handle_migrate(SpiceChannel *channel, SpiceMsgIn *in) { SpiceMsgOut *out; SpiceMsgIn *data = NULL; SpiceMsgMigrate *mig = spice_msg_in_parsed(in); SpiceChannelPrivate *c = channel->priv; g_message("spice_channel_handle_migrate.\n"); CHANNEL_DEBUG(channel, "%s: flags %u", __FUNCTION__, mig->flags); if (mig->flags & SPICE_MIGRATE_NEED_FLUSH) { g_message("++++++++++++ SPICE_MIGRATE_NEED_FLUSH.\n"); /* if peer version > 1: pushing the mark msg before all other messgages and sending it, * and only it */ if (c->peer_hdr.major_version == 1) { /* iterate_write is blocking and flushing all pending write */ SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel); } out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_FLUSH_MARK); spice_msg_out_send_internal(out); } if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) { g_message("+++++++++++ SPICE_MIGRATE_NEED_DATA_TRANSFER.\n"); spice_channel_recv_msg(channel, get_msg_handler, &data); if (!data) { g_critical("expected SPICE_MSG_MIGRATE_DATA, got empty message"); goto end; } else if (spice_header_get_msg_type(data->header, c->use_mini_header) != SPICE_MSG_MIGRATE_DATA) { g_critical("expected SPICE_MSG_MIGRATE_DATA, got %d", spice_header_get_msg_type(data->header, c->use_mini_header)); goto end; } } /* swapping channels sockets */ spice_session_channel_migrate(c->session, channel); /* pushing the MIGRATE_DATA before all other pending messages */ if ((mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) && (data != NULL)) { out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_DATA); spice_marshaller_add(out->marshaller, data->data, spice_header_get_msg_size(data->header, c->use_mini_header)); spice_msg_out_send_internal(out); } end: if (data) spice_msg_in_unref(data); }
/* coroutine context */ static void spice_channel_iterate_read(SpiceChannel *channel) { spice_channel *c = channel->priv; /* TODO: get rid of state, and use coroutine state */ switch (c->state) { case SPICE_CHANNEL_STATE_LINK_HDR: spice_channel_recv_link_hdr(channel); break; case SPICE_CHANNEL_STATE_LINK_MSG: spice_channel_recv_link_msg(channel); break; case SPICE_CHANNEL_STATE_AUTH: spice_channel_recv_auth(channel); break; case SPICE_CHANNEL_STATE_READY: spice_channel_recv_msg(channel, (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL); break; default: g_critical("unknown state %d", c->state); } }
/* coroutine context */ static void spice_channel_recv_link_hdr(SpiceChannel *channel) { spice_channel *c = channel->priv; int rc; rc = spice_channel_read(channel, &c->peer_hdr, sizeof(c->peer_hdr)); if (rc != sizeof(c->peer_hdr)) { g_critical("incomplete link header (%d/%" G_GSIZE_FORMAT ")", rc, sizeof(c->peer_hdr)); goto error; } if (c->peer_hdr.magic != SPICE_MAGIC) { g_critical("invalid SPICE_MAGIC!"); goto error; } if (c->peer_hdr.major_version != c->link_hdr.major_version) { if (c->peer_hdr.major_version == 1) { /* enter spice 0.4 mode */ g_object_set(c->session, "protocol", 1, NULL); SPICE_DEBUG("%s: switching to protocol 1 (spice 0.4)", c->name); SPICE_CHANNEL_GET_CLASS(channel)->channel_disconnect(channel); spice_channel_connect(channel); return; } g_critical("major mismatch (got %d, expected %d)", c->peer_hdr.major_version, c->link_hdr.major_version); goto error; } c->peer_msg = spice_malloc(c->peer_hdr.size); c->state = SPICE_CHANNEL_STATE_LINK_MSG; return; error: emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_LINK); }
/* coroutine context */ static void *spice_channel_coroutine(void *data) { SpiceChannel *channel = SPICE_CHANNEL(data); spice_channel *c = channel->priv; int ret; guint verify; SPICE_DEBUG("Started background coroutine %p", &c->coroutine); if (spice_session_get_client_provided_socket(c->session)) { if (c->fd < 0) { g_critical("fd not provided!"); goto cleanup; } if (!(c->sock = g_socket_new_from_fd(c->fd, NULL))) { SPICE_DEBUG("Failed to open socket from fd %d", c->fd); return FALSE; } g_socket_set_blocking(c->sock, FALSE); goto connected; } reconnect: c->sock = spice_session_channel_open_host(c->session, c->tls); if (c->sock == NULL) { if (!c->tls) { SPICE_DEBUG("connection failed, trying with TLS port"); c->tls = true; /* FIXME: does that really work with provided fd */ goto reconnect; } else { SPICE_DEBUG("Connect error"); emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_CONNECT); goto cleanup; } } c->has_error = FALSE; if (c->tls) { int rc; c->ctx = SSL_CTX_new(TLSv1_method()); if (c->ctx == NULL) { g_critical("SSL_CTX_new failed"); goto cleanup; } verify = spice_session_get_verify(c->session); if (verify & (SPICE_SESSION_VERIFY_PUBKEY | SPICE_SESSION_VERIFY_HOSTNAME)) { gchar *ca_file; g_object_get(c->session, "ca-file", &ca_file, NULL); if (ca_file) { rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL); if (rc != 1) g_warning("loading ca certs from %s failed", ca_file); g_free(ca_file); if (rc != 1) { if (verify & SPICE_SESSION_VERIFY_PUBKEY) { g_warning("only pubkey active"); verify = SPICE_SESSION_VERIFY_PUBKEY; } else goto cleanup; } } } c->ssl = SSL_new(c->ctx); if (c->ssl == NULL) { g_critical("SSL_new failed"); goto cleanup; } rc = SSL_set_fd(c->ssl, g_socket_get_fd(c->sock)); if (rc <= 0) { g_critical("SSL_set_fd failed"); goto cleanup; } { gchar *hostname, *subject; guint8 *pubkey; guint pubkey_len; g_object_get(c->session, "host", &hostname, "cert-subject", &subject, NULL); spice_session_get_pubkey(c->session, &pubkey, &pubkey_len); c->sslverify = spice_openssl_verify_new(c->ssl, verify, hostname, (char*)pubkey, pubkey_len, subject); g_free(hostname); g_free(subject); } ssl_reconnect: rc = SSL_connect(c->ssl); if (rc <= 0) { rc = SSL_get_error(c->ssl, rc); if (rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) { g_io_wait(c->sock, G_IO_OUT|G_IO_ERR|G_IO_HUP); goto ssl_reconnect; } else { g_warning("%s: SSL_connect: %s", c->name, ERR_error_string(rc, NULL)); emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS); goto cleanup; } } } connected: c->state = SPICE_CHANNEL_STATE_LINK_HDR; spice_channel_send_link(channel); while ((ret = spice_channel_iterate(channel))) ; /* TODO: improve it, this is a bit hairy, c->coroutine will be overwritten on (re)connect, so we skip the normal cleanup path. Ideally, we shouldn't use the same channel structure? */ if (c->state == SPICE_CHANNEL_STATE_CONNECTING) { g_object_unref(channel); goto end; } cleanup: SPICE_DEBUG("Coroutine exit"); SPICE_CHANNEL_GET_CLASS(channel)->channel_disconnect(channel); g_idle_add(spice_channel_delayed_unref, data); end: /* Co-routine exits now - the SpiceChannel object may no longer exist, so don't do anything else now unless you like SEGVs */ exit(1); return NULL; }