/* 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);
    }
}
Example #2
0
/* 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);
}
/* we use an idle function to allow the coroutine to exit before we actually
 * unref the object since the coroutine's state is part of the object */
static gboolean spice_channel_delayed_unref(gpointer data)
{
    SpiceChannel *channel = SPICE_CHANNEL(data);
    spice_channel *c = channel->priv;

    g_return_val_if_fail(channel != NULL, FALSE);
    SPICE_DEBUG("Delayed unref channel %s %p", c->name, channel);

    g_return_val_if_fail(c->coroutine.exited == TRUE, FALSE);

    g_object_unref(G_OBJECT(data));

    return FALSE;
}
static void virt_viewer_session_spice_clear_displays(VirtViewerSessionSpice *self)
{
    SpiceSession *session = self->priv->session;
    GList *l;
    GList *channels;

    channels = spice_session_get_channels(session);
    for (l = channels; l != NULL; l = l->next) {
        SpiceChannel *channel = SPICE_CHANNEL(l->data);

        g_object_set_data(G_OBJECT(channel), "virt-viewer-displays", NULL);
    }
    g_list_free(channels);
    virt_viewer_session_clear_displays(VIRT_VIEWER_SESSION(self));
}
static void spice_channel_constructed(GObject *gobject)
{
    SpiceChannel *channel = SPICE_CHANNEL(gobject);
    spice_channel *c = channel->priv;
    const char *desc = NULL;

    if (c->channel_type < SPICE_N_ELEMENTS(channel_desc))
        desc = channel_desc[c->channel_type];

    snprintf(c->name, sizeof(c->name), "%s-%d:%d",
             desc ? desc : "unknown", c->channel_type, c->channel_id);
    SPICE_DEBUG("%s: %s", c->name, __FUNCTION__);

    c->connection_id = spice_session_get_connection_id(c->session);
    spice_session_channel_new(c->session, channel);

    /* Chain up to the parent class */
    if (G_OBJECT_CLASS(spice_channel_parent_class)->constructed)
        G_OBJECT_CLASS(spice_channel_parent_class)->constructed(gobject);
}
static void spice_channel_dispose(GObject *gobject)
{
    SpiceChannel *channel = SPICE_CHANNEL(gobject);
    spice_channel *c = channel->priv;

    SPICE_DEBUG("%s: %s %p", c->name, __FUNCTION__, gobject);

    if (c->session)
        spice_session_channel_destroy(c->session, channel);

    spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);

    if (c->session) {
         g_object_unref(c->session);
         c->session = NULL;
    }

    /* Chain up to the parent class */
    if (G_OBJECT_CLASS(spice_channel_parent_class)->dispose)
        G_OBJECT_CLASS(spice_channel_parent_class)->dispose(gobject);
}
static void spice_channel_set_property(GObject      *gobject,
                                       guint         prop_id,
                                       const GValue *value,
                                       GParamSpec   *pspec)
{
    SpiceChannel *channel = SPICE_CHANNEL(gobject);
    spice_channel *c = channel->priv;

    switch (prop_id) {
    case PROP_SESSION:
        c->session = g_value_dup_object(value);
        break;
    case PROP_CHANNEL_TYPE:
        c->channel_type = g_value_get_int(value);
        break;
    case PROP_CHANNEL_ID:
        c->channel_id = g_value_get_int(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
        break;
    }
}
static void spice_channel_finalize(GObject *gobject)
{
    SpiceChannel *channel = SPICE_CHANNEL(gobject);
    spice_channel *c = channel->priv;

    SPICE_DEBUG("%s: %s %p", c->name, __FUNCTION__, gobject);

    if (c->caps)
        g_array_free(c->caps, TRUE);

    if (c->common_caps)
        g_array_free(c->common_caps, TRUE);

    if (c->remote_caps)
        g_array_free(c->remote_caps, TRUE);

    if (c->remote_common_caps)
        g_array_free(c->remote_common_caps, TRUE);

    /* Chain up to the parent class */
    if (G_OBJECT_CLASS(spice_channel_parent_class)->finalize)
        G_OBJECT_CLASS(spice_channel_parent_class)->finalize(gobject);
}
/**
 * spice_channel_new:
 * @s: the @SpiceSession the channel is linked to
 * @type: the requested SPICE_CHANNEL type
 * @id: the channel-id
 *
 * Create a new #SpiceChannel of type @type, and channel ID @id.
 *
 * Returns: a #SpiceChannel
 **/
SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
{
    SpiceChannel *channel;
    GType gtype = 0;

    g_return_val_if_fail(s != NULL, NULL);

    switch (type) {
    case SPICE_CHANNEL_MAIN:
        gtype = SPICE_TYPE_MAIN_CHANNEL;
        break;
    case SPICE_CHANNEL_DISPLAY:
        gtype = SPICE_TYPE_DISPLAY_CHANNEL;
        break;
    case SPICE_CHANNEL_CURSOR:
        //gtype = SPICE_TYPE_CURSOR_CHANNEL;
        break;
    case SPICE_CHANNEL_INPUTS:
        gtype = SPICE_TYPE_INPUTS_CHANNEL;
        break;
    case SPICE_CHANNEL_PLAYBACK:
        //gtype = SPICE_TYPE_PLAYBACK_CHANNEL;
        break;
    case SPICE_CHANNEL_RECORD:
        //gtype = SPICE_TYPE_RECORD_CHANNEL;
        break;
    default:
        return NULL;
    }
    channel = SPICE_CHANNEL(g_object_new(gtype,
                                         "spice-session", s,
                                         "channel-type", type,
                                         "channel-id", id,
                                         NULL));
    return channel;
}
/* 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;
}