/* Copyright (C) 2009 Red Hat, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spice.h" #include "reds.h" #include "red.h" #include "vd_agent.h" #include "red_common.h" #include "red_dispatcher.h" #include "snd_worker.h" #include "reds_stat.h" #include "stat.h" #include "ring.h" #include "config.h" CoreInterface *core = NULL; static MigrationInterface *mig = NULL; static KeyboardInterface *keyboard = NULL; static MouseInterface *mouse = NULL; static TabletInterface *tablet = NULL; static VDIPortInterface *vdagent = NULL; #define MIGRATION_NOTIFY_SPICE_KEY "spice_mig_ext" #define REDS_MIG_VERSION 3 #define REDS_MIG_CONTINUE 1 #define REDS_MIG_ABORT 2 #define REDS_MIG_DIFF_VERSION 3 #define REDS_AGENT_WINDOW_SIZE 10 #define REDS_TOKENS_TO_SEND 5 #define REDS_NUM_INTERNAL_AGENT_MESSAGES 1 #define REDS_VDI_PORT_NUM_RECIVE_BUFFS 5 #define REDS_MAX_SEND_IOVEC 100 #define NET_TEST_WARMUP_BYTES 0 #define NET_TEST_BYTES (1024 * 250) static int spice_port = -1; static int spice_secure_port = -1; static char spice_addr[256]; static int spice_family = PF_UNSPEC; static char *default_renderer = "cairo"; static int ticketing_enabled = 1; //Ticketing is enabled by default static pthread_mutex_t *lock_cs; static long *lock_count; uint32_t streaming_video = STREAM_VIDEO_FILTER; spice_image_compression_t image_compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ; int agent_mouse = TRUE; static void openssl_init(); #define MIGRATE_TIMEOUT (1000 * 10) /* 10sec */ #define PING_INTERVAL (1000 * 10) #define KEY_MODIFIERS_TTL (1000 * 2) /*2sec*/ #define MM_TIMER_GRANULARITY_MS (1000 / 30) #define MM_TIME_DELTA 400 /*ms*/ // approximate max recive message size #define RECIVE_BUF_SIZE \ (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * RED_AGENT_MAX_DATA_SIZE) #define SEND_BUF_SIZE 4096 #define SCROLL_LOCK_SCAN_CODE 0x46 #define NUM_LOCK_SCAN_CODE 0x45 #define CAPS_LOCK_SCAN_CODE 0x3a typedef struct IncomingHandler { void *opaque; int shut; uint8_t buf[RECIVE_BUF_SIZE]; uint32_t end_pos; void (*handle_message)(void *opaque, RedDataHeader *message); } IncomingHandler; typedef struct OutgoingHandler { void *opaque; uint8_t buf[SEND_BUF_SIZE]; uint8_t *now; uint32_t length; void (*select)(void *opaque, int select); void (*may_write)(void *opaque); } OutgoingHandler; typedef struct TicketAuthentication { char password[RED_MAX_PASSWORD_LENGTH]; time_t expiration_time; } TicketAuthentication; static TicketAuthentication taTicket; typedef struct TicketInfo { RSA *rsa; int rsa_size; BIGNUM *bn; RedLinkEncryptedTicket encrypted_ticket; } TicketInfo; typedef struct MonitorMode { uint32_t x_res; uint32_t y_res; } MonitorMode; typedef struct RedsOutItem RedsOutItem; struct RedsOutItem { RingItem link; void (*prepare)(RedsOutItem *item, struct iovec* vec, int *len); void (*release)(RedsOutItem *item); }; typedef struct VDIReadBuf { RedsOutItem out_item; int len; RedDataHeader header; uint8_t data[RED_AGENT_MAX_DATA_SIZE]; } VDIReadBuf; enum { VDI_PORT_READ_STATE_READ_HADER, VDI_PORT_READ_STATE_GET_BUFF, VDI_PORT_READ_STATE_READ_DATA, }; enum { VDP_CLIENT_PORT = 1, VDP_SERVER_PORT, }; typedef struct __attribute__ ((__packed__)) VDIChunkHeader { uint32_t port; uint32_t size; } VDIChunkHeader; typedef struct VDIPortState { VDIPortPlug plug; VDObjectRef plug_ref; uint32_t plug_generation; uint32_t num_tokens; uint32_t num_client_tokens; Ring external_bufs; Ring internal_bufs; Ring write_queue; Ring read_bufs; uint32_t read_state; uint32_t message_recive_len; uint8_t *recive_pos; uint32_t recive_len; VDIReadBuf *current_read_buf; VDIChunkHeader vdi_chunk_header; int client_agent_started; uint32_t send_tokens; } VDIPortState; typedef struct InputsState { Channel *channel; RedsStreamContext *peer; uint8_t buf[RECIVE_BUF_SIZE]; uint32_t end_pos; IncomingHandler in_handler; OutgoingHandler out_handler; VDAgentMouseState mouse_state; int pending_mouse_event; uint32_t motion_count; uint64_t serial; //migrate me } InputsState; typedef struct RedsOutgoingData { Ring pipe; RedsOutItem *item; int vec_size; struct iovec vec_buf[REDS_MAX_SEND_IOVEC]; struct iovec *vec; } RedsOutgoingData; enum NetTestStage { NET_TEST_STAGE_INVALID, NET_TEST_STAGE_WARMUP, NET_TEST_STAGE_LATENCY, NET_TEST_STAGE_RATE, }; #ifdef RED_STATISTICS #define REDS_MAX_STAT_NODES 100 #define REDS_STAT_SHM_SIZE (sizeof(RedsStat) + REDS_MAX_STAT_NODES * sizeof(StatNode)) typedef struct RedsStatValue { uint32_t value; uint32_t min; uint32_t max; uint32_t average; uint32_t count; } RedsStatValue; #endif typedef struct SimpleOutItem SimpleOutItem; typedef struct RedsState { int listen_socket; int secure_listen_socket; RedsStreamContext *peer; int disconnecting; uint32_t link_id; uint64_t serial; //migrate me VDIPortState agent_state; InputsState *inputs_state; VDObjectRef mig_notifier; int mig_wait_connect; int mig_wait_disconnect; int mig_inprogress; int mig_target; int num_of_channels; IncomingHandler in_handler; RedsOutgoingData outgoing; Channel *channels; int mouse_mode; int is_client_mouse_allowed; int dispatcher_allows_client_mouse; MonitorMode monitor_mode; VDObjectRef mig_timer; VDObjectRef key_modifiers_timer; VDObjectRef mm_timer; TicketAuthentication taTicket; SSL_CTX *ctx; #ifdef RED_STATISTICS char *stat_shm_name; RedsStat *stat; pthread_mutex_t stat_lock; RedsStatValue roundtrip_stat; VDObjectRef ping_timer; int ping_interval; #endif uint32_t ping_id; uint32_t net_test_id; int net_test_stage; int peer_minor_version; SimpleOutItem* mig_switch_host_item; } RedsState; uint64_t bitrate_per_sec = ~0; static uint64_t letancy = 0; static RedsState *reds = NULL; typedef struct AsyncRead { RedsStreamContext *peer; void *opaque; uint8_t *now; uint8_t *end; int active_file_handlers; void (*done)(void *opaque); void (*error)(void *opaque, int err); } AsyncRead; typedef struct RedLinkInfo { RedsStreamContext *peer; AsyncRead asyc_read; RedLinkHeader link_header; RedLinkMess *link_mess; int mess_pos; TicketInfo tiTicketing; } RedLinkInfo; typedef struct VDIPortBuf VDIPortBuf; struct __attribute__ ((__packed__)) VDIPortBuf { RingItem link; uint8_t *now; int write_len; void (*free)(VDIPortBuf *buf); VDIChunkHeader chunk_header; //start send from &chunk_header }; typedef struct __attribute__ ((__packed__)) VDAgentExtBuf { VDIPortBuf base; uint8_t buf[RED_AGENT_MAX_DATA_SIZE]; VDIChunkHeader migrate_overflow; } VDAgentExtBuf; typedef struct __attribute__ ((__packed__)) VDInternalBuf { VDIPortBuf base; VDAgentMessage header; union { VDAgentMouseState mouse_state; } u; VDIChunkHeader migrate_overflow; } VDInternalBuf; typedef struct RedSSLParameters { char keyfile_password[256]; char certs_file[256]; char private_key_file[256]; char ca_certificate_file[256]; char dh_key_file[256]; char ciphersuite[256]; } RedSSLParameters; typedef struct ChannelSecurityOptions ChannelSecurityOptions; struct ChannelSecurityOptions { uint32_t channel_id; uint32_t options; ChannelSecurityOptions *next; }; typedef struct PingItem { RedsOutItem base; RedDataHeader header; RedPing ping; int size; } PingItem; #define ZERO_BUF_SIZE 4096 static uint8_t zero_page[ZERO_BUF_SIZE] = {0}; static void reds_main_write(void *data); static void reds_push(); static ChannelSecurityOptions *channels_security = NULL; static int default_channel_security = SPICE_CHANNEL_SECURITY_NON | SPICE_CHANNEL_SECURITY_SSL; static RedSSLParameters ssl_parameters; void (*log_proc)(CoreInterface *core, LogLevel level, const char* component, const char* format, ...) = NULL; #define LOG_MESSAGE(level, format, ...) { \ if (log_proc) { \ log_proc(core, level, "spice", format, ## __VA_ARGS__ ); \ } \ } static int args_is_empty(const VDICmdArg* args) { return !args || args[0].descriptor.type == ARG_TYPE_INVALID; } const int args_is_string(const VDICmdArg* args) { return !args_is_empty(args) && args->descriptor.type == ARG_TYPE_STRING; } const int args_is_int(const VDICmdArg* args) { return !args_is_empty(args) && args->descriptor.type == ARG_TYPE_INT; } static ChannelSecurityOptions *find_channel_security(int id) { ChannelSecurityOptions *now = channels_security; while (now && now->channel_id != id) { now = now->next; } return now; } static int reds_write(void *ctx, void *buf, size_t size) { int return_code; int sock = (long)ctx; size_t count = size; return_code = write(sock, buf, count); return (return_code); } static int reds_read(void *ctx, void *buf, size_t size) { int return_code; int sock = (long)ctx; size_t count = size; return_code = read(sock, buf, count); return (return_code); } static int reds_free(RedsStreamContext *peer) { close(peer->socket); free(peer); return 0; } static int reds_ssl_write(void *ctx, void *buf, size_t size) { int return_code; int ssl_error; SSL *ssl = ctx; return_code = SSL_write(ssl, buf, size); if (return_code < 0) { ssl_error = SSL_get_error(ssl, return_code); } return (return_code); } static int reds_ssl_read(void *ctx, void *buf, size_t size) { int return_code; int ssl_error; SSL *ssl = ctx; return_code = SSL_read(ssl, buf, size); if (return_code < 0) { ssl_error = SSL_get_error(ssl, return_code); } return (return_code); } static int reds_ssl_writev(void *ctx, const struct iovec *vector, int count) { int i; int n; int return_code = 0; int ssl_error; SSL *ssl = ctx; for (i = 0; i < count; ++i) { n = SSL_write(ssl, vector[i].iov_base, vector[i].iov_len); if (n <= 0) { ssl_error = SSL_get_error(ssl, n); if (return_code <= 0) { return n; } else { break; } } else { return_code += n; } } return return_code; } static int reds_ssl_free(RedsStreamContext *peer) { SSL_free(peer->ssl); close(peer->socket); free(peer); return 0; } static void __reds_release_link(RedLinkInfo *link) { ASSERT(link->peer); core->set_file_handlers(core, link->peer->socket, NULL, NULL, NULL); free(link->link_mess); BN_free(link->tiTicketing.bn); if (link->tiTicketing.rsa) { RSA_free(link->tiTicketing.rsa); } free(link); } static inline void reds_release_link(RedLinkInfo *link) { RedsStreamContext *peer = link->peer; __reds_release_link(link); peer->cb_free(peer); } static void reds_do_disable_ticketing(void) { ticketing_enabled = 0; memset(taTicket.password, 0, sizeof(taTicket.password)); core->term_printf(core, "Ticketing is now disabled.\n"); } static void reds_do_disable_ticketing_2(const VDICmdArg* args) { if (!args_is_empty(args)) { red_printf("invalid args"); return; } reds_do_disable_ticketing(); } static char *base64decode(const char *input, int length) { BIO *b64; BIO *bmem; int n; char *buffer = (char *)malloc(length); memset(buffer, 0, length); char *inbuffer = (char *)malloc(length + 1); memset(inbuffer, 0, length + 1); memcpy(inbuffer, input, length); inbuffer[length] = '\n'; b64 = BIO_new(BIO_f_base64()); bmem = BIO_new_mem_buf(inbuffer, length + 1); if (b64 != NULL && bmem != NULL) { bmem = BIO_push(b64, bmem); n = BIO_read(bmem, buffer, length); if (n != 0) { buffer[n - 1] = '\0'; } else { free(buffer); buffer = NULL; } } else { free(buffer); buffer = NULL; } BIO_free_all(bmem); return buffer; } static void reds_do_info_ticket(void) { core->term_printf(core, "Ticket Information:"); if (ticketing_enabled) { if (strlen(taTicket.password) == 0) { core->term_printf(core, " blocked\n"); } else { if (taTicket.expiration_time == INT_MAX) { core->term_printf(core, " expiration NEVER\n"); } else { time_t now; time(&now); int expired = taTicket.expiration_time < now; if (expired) { core->term_printf(core, " expiration EXPIRED\n"); } else { core->term_printf(core, " expiration %s\n", ctime((time_t *)&(taTicket.expiration_time))); } } } } else { core->term_printf(core, " disabled\n"); } } static struct iovec *reds_iovec_skip(struct iovec vec[], int skip, int *vec_size) { struct iovec *now = vec; while (skip && skip >= now->iov_len) { skip -= now->iov_len; --*vec_size; now++; } now->iov_base = (uint8_t *)now->iov_base + skip; now->iov_len -= skip; return now; } #ifdef RED_STATISTICS #define STAT_TAB_LEN 4 #define STAT_VALUE_TABS 7 static void print_stat_tree(uint32_t node_index, int depth) { StatNode *node = &reds->stat->nodes[node_index]; if ((node->flags & STAT_NODE_MASK_SHOW) == STAT_NODE_MASK_SHOW) { core->term_printf(core, "%*s%s", depth * STAT_TAB_LEN, "", node->name); if (node->flags & STAT_NODE_FLAG_VALUE) { core->term_printf(core, ":%*s%llu\n", (STAT_VALUE_TABS - depth) * STAT_TAB_LEN - strlen(node->name) - 1, "", node->value); } else { core->term_printf(core, "\n"); if (node->first_child_index != INVALID_STAT_REF) { print_stat_tree(node->first_child_index, depth + 1); } } } if (node->next_sibling_index != INVALID_STAT_REF) { print_stat_tree(node->next_sibling_index, depth); } } static void do_info_statistics() { core->term_printf(core, "Spice Statistics:\n"); print_stat_tree(reds->stat->root_index, 0); } static void do_reset_statistics() { StatNode *node; int i; for (i = 0; i <= REDS_MAX_STAT_NODES; i++) { node = &reds->stat->nodes[i]; if (node->flags & STAT_NODE_FLAG_VALUE) { node->value = 0; } } } static void do_reset_statistics_2(const VDICmdArg* args) { if (!args_is_empty(args)) { red_printf("invalid args"); return; } do_reset_statistics(); } void insert_stat_node(StatNodeRef parent, StatNodeRef ref) { StatNode *node = &reds->stat->nodes[ref]; uint32_t pos = INVALID_STAT_REF; uint32_t node_index; uint32_t *head; StatNode *n; node->first_child_index = INVALID_STAT_REF; head = (parent == INVALID_STAT_REF ? &reds->stat->root_index : &reds->stat->nodes[parent].first_child_index); node_index = *head; while (node_index != INVALID_STAT_REF && (n = &reds->stat->nodes[node_index]) && strcmp(node->name, n->name) > 0) { pos = node_index; node_index = n->next_sibling_index; } if (pos == INVALID_STAT_REF) { node->next_sibling_index = *head; *head = ref; } else { n = &reds->stat->nodes[pos]; node->next_sibling_index = n->next_sibling_index; n->next_sibling_index = ref; } } StatNodeRef stat_add_node(StatNodeRef parent, const char *name, int visible) { StatNodeRef ref; StatNode *node; ASSERT(name && strlen(name) > 0); if (strlen(name) >= sizeof(node->name)) { return INVALID_STAT_REF; } pthread_mutex_lock(&reds->stat_lock); ref = (parent == INVALID_STAT_REF ? reds->stat->root_index : reds->stat->nodes[parent].first_child_index); while (ref != INVALID_STAT_REF) { node = &reds->stat->nodes[ref]; if (strcmp(name, node->name)) { ref = node->next_sibling_index; } else { pthread_mutex_unlock(&reds->stat_lock); return ref; } } if (reds->stat->num_of_nodes >= REDS_MAX_STAT_NODES || reds->stat == NULL) { pthread_mutex_unlock(&reds->stat_lock); return INVALID_STAT_REF; } reds->stat->generation++; reds->stat->num_of_nodes++; for (ref = 0; ref <= REDS_MAX_STAT_NODES; ref++) { node = &reds->stat->nodes[ref]; if (!(node->flags & STAT_NODE_FLAG_ENABLED)) { break; } } ASSERT(!(node->flags & STAT_NODE_FLAG_ENABLED)); node->value = 0; node->flags = STAT_NODE_FLAG_ENABLED | (visible ? STAT_NODE_FLAG_VISIBLE : 0); strncpy(node->name, name, sizeof(node->name)); insert_stat_node(parent, ref); pthread_mutex_unlock(&reds->stat_lock); return ref; } void stat_remove(StatNode *node) { pthread_mutex_lock(&reds->stat_lock); node->flags &= ~STAT_NODE_FLAG_ENABLED; reds->stat->generation++; reds->stat->num_of_nodes--; pthread_mutex_unlock(&reds->stat_lock); } void stat_remove_node(StatNodeRef ref) { stat_remove(&reds->stat->nodes[ref]); } uint64_t *stat_add_counter(StatNodeRef parent, const char *name, int visible) { StatNodeRef ref = stat_add_node(parent, name, visible); StatNode *node; if (ref == INVALID_STAT_REF) { return NULL; } node = &reds->stat->nodes[ref]; node->flags |= STAT_NODE_FLAG_VALUE; return &node->value; } void stat_remove_counter(uint64_t *counter) { stat_remove((StatNode *)(counter - offsetof(StatNode, value))); } static void reds_update_stat_value(RedsStatValue* stat_value, uint32_t value) { stat_value->value = value; stat_value->min = (stat_value->count ? MIN(stat_value->min, value) : value); stat_value->max = MAX(stat_value->max, value); stat_value->average = (stat_value->average * stat_value->count + value) / (stat_value->count + 1); stat_value->count++; } #endif void reds_register_channel(Channel *channel) { ASSERT(reds); channel->next = reds->channels; reds->channels = channel; reds->num_of_channels++; } void reds_unregister_channel(Channel *channel) { Channel **now = &reds->channels; while (*now) { if (*now == channel) { *now = channel->next; reds->num_of_channels--; return; } now = &(*now)->next; } red_printf("not found"); } static Channel *reds_find_channel(uint32_t type, uint32_t id) { Channel *channel = reds->channels; while (channel && !(channel->type == type && channel->id == id)) { channel = channel->next; } return channel; } static void reds_shatdown_channels() { Channel *channel = reds->channels; while (channel) { channel->shutdown(channel); channel = channel->next; } } static void reds_mig_cleanup() { if (reds->mig_inprogress) { reds->mig_inprogress = FALSE; reds->mig_wait_connect = FALSE; reds->mig_wait_disconnect = FALSE; core->disarm_timer(core, reds->mig_timer); mig->notifier_done(mig, reds->mig_notifier); } } static void reds_reset_vdp() { VDIPortState *state = &reds->agent_state; while (!ring_is_empty(&state->write_queue)) { VDIPortBuf *buf; RingItem *item; item = ring_get_tail(&state->write_queue); ring_remove(item); buf = (VDIPortBuf *)item; buf->free(buf); } state->read_state = VDI_PORT_READ_STATE_READ_HADER; state->recive_pos = (uint8_t *)&state->vdi_chunk_header; state->recive_len = sizeof(state->vdi_chunk_header); state->message_recive_len = 0; if (state->current_read_buf) { ring_add(&state->read_bufs, &state->current_read_buf->out_item.link); state->current_read_buf = NULL; } state->client_agent_started = FALSE; state->send_tokens = 0; } static void reds_reset_outgoing() { RedsOutgoingData *outgoing = &reds->outgoing; RingItem *ring_item; if (outgoing->item) { outgoing->item->release(outgoing->item); outgoing->item = NULL; } while ((ring_item = ring_get_tail(&outgoing->pipe))) { RedsOutItem *out_item = (RedsOutItem *)ring_item; ring_remove(ring_item); out_item->release(out_item); } outgoing->vec_size = 0; outgoing->vec = outgoing->vec_buf; } static void reds_disconnect() { if (!reds->peer || reds->disconnecting) { return; } red_printf(""); LOG_MESSAGE(VD_LOG_INFO, "user disconnected"); reds->disconnecting = TRUE; reds_reset_outgoing(); if (reds->agent_state.plug_ref != INVALID_VD_OBJECT_REF) { ASSERT(vdagent); vdagent->unplug(vdagent, reds->agent_state.plug_ref); reds->agent_state.plug_ref = INVALID_VD_OBJECT_REF; reds_reset_vdp(); } reds_shatdown_channels(); core->set_file_handlers(core, reds->peer->socket, NULL, NULL, NULL); reds->peer->cb_free(reds->peer); reds->peer = NULL; reds->in_handler.shut = TRUE; reds->link_id = 0; reds->serial = 0; reds->ping_id = 0; reds->net_test_id = 0; reds->net_test_stage = NET_TEST_STAGE_INVALID; reds->in_handler.end_pos = 0; bitrate_per_sec = ~0; letancy = 0; reds_mig_cleanup(); reds->disconnecting = FALSE; } static void reds_mig_disconnect() { if (reds->peer) { reds_disconnect(); } else { reds_mig_cleanup(); } } static int handle_incoming(RedsStreamContext *peer, IncomingHandler *handler) { for (;;) { uint8_t *buf = handler->buf; uint32_t pos = handler->end_pos; uint8_t *end = buf + pos; RedDataHeader *header; int n; n = peer->cb_read(peer->ctx, buf + pos, RECIVE_BUF_SIZE - pos); if (n <= 0) { if (n == 0) { return -1; } switch (errno) { case EAGAIN: return 0; case EINTR: break; case EPIPE: return -1; default: red_printf("%s", strerror(errno)); return -1; } } else { pos += n; end = buf + pos; while (buf + sizeof(RedDataHeader) <= end && buf + sizeof(RedDataHeader) + (header = (RedDataHeader *)buf)->size <= end) { buf += sizeof(RedDataHeader) + header->size; handler->handle_message(handler->opaque, header); if (handler->shut) { return -1; } } memmove(handler->buf, buf, (handler->end_pos = end - buf)); } } } static int handle_outgoing(RedsStreamContext *peer, OutgoingHandler *handler) { if (!handler->length) { return 0; } while (handler->length) { int n; n = peer->cb_write(peer->ctx, handler->now, handler->length); if (n <= 0) { if (n == 0) { return -1; } switch (errno) { case EAGAIN: return 0; case EINTR: break; case EPIPE: return -1; default: red_printf("%s", strerror(errno)); return -1; } } else { handler->now += n; handler->length -= n; } } handler->select(handler->opaque, FALSE); handler->may_write(handler->opaque); return 0; } #define OUTGOING_OK 0 #define OUTGOING_FAILED -1 #define OUTGOING_BLOCKED 1 static int outgoing_write(RedsStreamContext *peer, OutgoingHandler *handler, void *in_data, int length) { uint8_t *data = in_data; ASSERT(length <= SEND_BUF_SIZE); if (handler->length) { return OUTGOING_BLOCKED; } while (length) { int n = peer->cb_write(peer->ctx, data, length); if (n < 0) { switch (errno) { case EAGAIN: handler->length = length; memcpy(handler->buf, data, length); handler->select(handler->opaque, TRUE); return OUTGOING_OK; case EINTR: break; case EPIPE: return OUTGOING_FAILED; default: red_printf("%s", strerror(errno)); return OUTGOING_FAILED; } } else { data += n; length -= n; } } return OUTGOING_OK; } struct SimpleOutItem { RedsOutItem base; RedDataHeader header; uint8_t data[0]; }; static void reds_prepare_basic_out_item(RedsOutItem *in_item, struct iovec* vec, int *len) { SimpleOutItem *item = (SimpleOutItem *)in_item; vec[0].iov_base = &item->header; vec[0].iov_len = sizeof(item->header); if (item->header.size) { vec[1].iov_base = item->data; vec[1].iov_len = item->header.size; *len = 2; } else { *len = 1; } } static void reds_free_basic_out_item(RedsOutItem *item) { free(item); } static SimpleOutItem *new_simple_out_item(uint32_t type, int message_size) { SimpleOutItem *item; if (!(item = (SimpleOutItem *)malloc(sizeof(*item) + message_size))) { return NULL; } ring_item_init(&item->base.link); item->base.prepare = reds_prepare_basic_out_item; item->base.release = reds_free_basic_out_item; item->header.serial = ++reds->serial; item->header.type = type; item->header.size = message_size; item->header.sub_list = 0; return item; } static void reds_push_pipe_item(RedsOutItem *item) { ring_add(&reds->outgoing.pipe, &item->link); reds_push(); } static void reds_send_channels() { RedChannels* channels_info; SimpleOutItem *item; int message_size; Channel *channel; int i; message_size = sizeof(RedChannels) + reds->num_of_channels * sizeof(RedChannelInit); if (!(item = new_simple_out_item(RED_CHANNELS_LIST, message_size))) { red_printf("alloc item failed"); reds_disconnect(); return; } channels_info = (RedChannels *)item->data; channels_info->num_of_channels = reds->num_of_channels; channel = reds->channels; for (i = 0; i < reds->num_of_channels; i++) { ASSERT(channel); channels_info->channels[i].type = channel->type; channels_info->channels[i].id = channel->id; channel = channel->next; } reds_push_pipe_item(&item->base); } static void reds_prepare_ping_item(RedsOutItem *in_item, struct iovec* vec, int *len) { PingItem *item = (PingItem *)in_item; vec[0].iov_base = &item->header; vec[0].iov_len = sizeof(item->header); vec[1].iov_base = &item->ping; vec[1].iov_len = sizeof(item->ping); int size = item->size; int pos = 2; while (size) { ASSERT(pos < REDS_MAX_SEND_IOVEC); int now = MIN(ZERO_BUF_SIZE, size); size -= now; vec[pos].iov_base = zero_page; vec[pos].iov_len = now; pos++; } *len = pos; } static void reds_free_ping_item(RedsOutItem *item) { free(item); } static int send_ping(int size) { struct timespec time_space; PingItem *item; if (!reds->peer || !(item = (PingItem *)malloc(sizeof(*item)))) { return FALSE; } ring_item_init(&item->base.link); item->base.prepare = reds_prepare_ping_item; item->base.release = reds_free_ping_item; item->header.serial = ++reds->serial; item->header.type = RED_PING; item->header.size = sizeof(item->ping) + size; item->header.sub_list = 0; item->ping.id = ++reds->ping_id; clock_gettime(CLOCK_MONOTONIC, &time_space); item->ping.timestamp = time_space.tv_sec * 1000000LL + time_space.tv_nsec / 1000LL; item->size = size; reds_push_pipe_item(&item->base); return TRUE; } #ifdef RED_STATISTICS static void do_ping_client(const char *opt, int has_interval, int interval) { if (!reds->peer) { red_printf("not connected to peer"); return; } if (!opt) { send_ping(0); } else if (!strcmp(opt, "on")) { if (has_interval && interval > 0) { reds->ping_interval = interval * 1000; } core->arm_timer(core, reds->ping_timer, reds->ping_interval); core->term_printf(core, "ping on, interval %u s\n", reds->ping_interval / 1000); } else if (!strcmp(opt, "off")) { core->disarm_timer(core, reds->ping_timer); core->term_printf(core, "ping off\n"); } else { core->term_printf(core, "ping invalid option: %s\n", opt); return; } } static void do_ping_client_2(const VDICmdArg* args) { if (args_is_empty(args)) { do_ping_client(NULL, FALSE, 0); return; } if (!args_is_string(args)) { red_printf("invalid args"); return; } if (args_is_empty(&args[1])) { do_ping_client(args[0].string_val, FALSE, 0); return; } if (!args_is_int(&args[1])) { red_printf("invalid args"); return; } do_ping_client(args[0].string_val, TRUE, args[1].int_val); } static void ping_timer_cb() { if (!reds->peer) { red_printf("not connected to peer, ping off"); core->disarm_timer(core, reds->ping_timer); return; } do_ping_client(NULL, 0, 0); core->arm_timer(core, reds->ping_timer, reds->ping_interval); } static void do_info_rtt_client() { core->term_printf(core, "rtt=%uus, min/max/avg=%u/%u/%uus\n", reds->roundtrip_stat.value, reds->roundtrip_stat.min, reds->roundtrip_stat.max, reds->roundtrip_stat.average); } #endif static void reds_send_mouse_mode() { RedMouseMode *mouse_mode; SimpleOutItem *item; if (!reds->peer) { return; } if (!(item = new_simple_out_item(RED_MOUSE_MODE, sizeof(RedMouseMode)))) { red_printf("alloc item failed"); reds_disconnect(); return; } mouse_mode = (RedMouseMode *)item->data; mouse_mode->supported_modes = RED_MOUSE_MODE_SERVER; if (reds->is_client_mouse_allowed) { mouse_mode->supported_modes |= RED_MOUSE_MODE_CLIENT; } mouse_mode->current_mode = reds->mouse_mode; reds_push_pipe_item(&item->base); } static void reds_set_mouse_mode(uint32_t mode) { if (reds->mouse_mode == mode) { return; } reds->mouse_mode = mode; red_dispatcher_set_mouse_mode(reds->mouse_mode); reds_send_mouse_mode(); } static void reds_update_mouse_mode() { int allowed = 0; int qxl_count = red_dispatcher_qxl_count(); if ((agent_mouse && vdagent) || (tablet && qxl_count == 1)) { allowed = reds->dispatcher_allows_client_mouse; } if (allowed == reds->is_client_mouse_allowed) { return; } reds->is_client_mouse_allowed = allowed; if (reds->mouse_mode == RED_MOUSE_MODE_CLIENT && !allowed) { reds_set_mouse_mode(RED_MOUSE_MODE_SERVER); return; } reds_send_mouse_mode(); } static void reds_send_agent_connected() { SimpleOutItem *item; if (!(item = new_simple_out_item(RED_AGENT_CONNECTED, 0))) { PANIC("alloc item failed"); } reds_push_pipe_item(&item->base); } static void reds_send_agent_disconnected() { RedAgentDisconnect *disconnect; SimpleOutItem *item; if (!(item = new_simple_out_item(RED_AGENT_DISCONNECTED, sizeof(RedAgentDisconnect)))) { PANIC("alloc item failed"); } disconnect = (RedAgentDisconnect *)item->data; disconnect->error_code = RED_ERR_OK; reds_push_pipe_item(&item->base); } static void reds_agent_remove() { VDIPortInterface *interface = vdagent; vdagent = NULL; reds_update_mouse_mode(); if (!reds->peer || !interface) { return; } ASSERT(reds->agent_state.plug_ref != INVALID_VD_OBJECT_REF); interface->unplug(interface, reds->agent_state.plug_ref); reds->agent_state.plug_ref = INVALID_VD_OBJECT_REF; if (reds->mig_target) { return; } reds_reset_vdp(); reds_send_agent_disconnected(); } static void reds_send_tokens() { RedAgentTokens *tokens; SimpleOutItem *item; if (!reds->peer) { return; } if (!(item = new_simple_out_item(RED_AGENT_TOKEN, sizeof(RedAgentTokens)))) { red_printf("alloc item failed"); reds_disconnect(); return; } tokens = (RedAgentTokens *)item->data; tokens->num_tokens = reds->agent_state.num_tokens; reds->agent_state.num_client_tokens += tokens->num_tokens; ASSERT(reds->agent_state.num_client_tokens <= REDS_AGENT_WINDOW_SIZE); reds->agent_state.num_tokens = 0; reds_push_pipe_item(&item->base); } static int write_to_vdi_port() { VDIPortState *state = &reds->agent_state; RingItem *ring_item; VDIPortBuf *buf; int total = 0; int n; if (reds->agent_state.plug_ref == INVALID_VD_OBJECT_REF || reds->mig_target) { return 0; } for (;;) { if (!(ring_item = ring_get_tail(&state->write_queue))) { break; } buf = (VDIPortBuf *)ring_item; n = vdagent->write(vdagent, state->plug_ref, buf->now, buf->write_len); if (n == 0) { break; } total += n; buf->write_len -= n; if (!buf->write_len) { ring_remove(ring_item); buf->free(buf); continue; } buf->now += n; } return total; } static void dispatch_vdi_port_data(int port, VDIReadBuf *buf) { VDIPortState *state = &reds->agent_state; switch (port) { case VDP_CLIENT_PORT: { buf->header.serial = ++reds->serial; buf->header.size = buf->len; reds_push_pipe_item(&buf->out_item); break; } case VDP_SERVER_PORT: ring_add(&state->read_bufs, &buf->out_item.link); break; default: ring_add(&state->read_bufs, &buf->out_item.link); red_printf("invalid port"); reds_agent_remove(); } } static int read_from_vdi_port() { VDIPortState *state = &reds->agent_state; VDIReadBuf *dispatch_buf; int total = 0; int n; if (reds->mig_target) { return 0; } while (reds->agent_state.plug_ref != INVALID_VD_OBJECT_REF) { switch (state->read_state) { case VDI_PORT_READ_STATE_READ_HADER: n = vdagent->read(vdagent, state->plug_ref, state->recive_pos, state->recive_len); if (!n) { return total; } total += n; if ((state->recive_len -= n)) { state->recive_pos += n; break; } state->message_recive_len = state->vdi_chunk_header.size; state->read_state = VDI_PORT_READ_STATE_GET_BUFF; case VDI_PORT_READ_STATE_GET_BUFF: { RingItem *item; if (!(item = ring_get_head(&state->read_bufs))) { return total; } if (state->vdi_chunk_header.port == VDP_CLIENT_PORT) { if (!state->send_tokens) { return total; } --state->send_tokens; } ring_remove(item); state->current_read_buf = (VDIReadBuf *)item; state->recive_pos = state->current_read_buf->data; state->recive_len = MIN(state->message_recive_len, sizeof(state->current_read_buf->data)); state->current_read_buf->len = state->recive_len; state->message_recive_len -= state->recive_len; state->read_state = VDI_PORT_READ_STATE_READ_DATA; } case VDI_PORT_READ_STATE_READ_DATA: n = vdagent->read(vdagent, state->plug_ref, state->recive_pos, state->recive_len); if (!n) { return total; } total += n; if ((state->recive_len -= n)) { state->recive_pos += n; break; } dispatch_buf = state->current_read_buf; state->current_read_buf = NULL; state->recive_pos = NULL; if (state->message_recive_len == 0) { state->read_state = VDI_PORT_READ_STATE_READ_HADER; state->recive_pos = (uint8_t *)&state->vdi_chunk_header; state->recive_len = sizeof(state->vdi_chunk_header); } else { state->read_state = VDI_PORT_READ_STATE_GET_BUFF; } dispatch_vdi_port_data(state->vdi_chunk_header.port, dispatch_buf); } } return total; } static void reds_agent_wakeup(VDIPortPlug *plug) { while (write_to_vdi_port() || read_from_vdi_port()); } static void reds_handle_agent_mouse_event() { RingItem *ring_item; VDInternalBuf *buf; if (!reds->inputs_state) { return; } if (reds->mig_target || !(ring_item = ring_get_head(&reds->agent_state.internal_bufs))) { reds->inputs_state->pending_mouse_event = TRUE; return; } reds->inputs_state->pending_mouse_event = FALSE; ring_remove(ring_item); buf = (VDInternalBuf *)ring_item; buf->base.now = (uint8_t *)&buf->base.chunk_header; buf->base.write_len = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + sizeof(VDAgentMouseState); buf->u.mouse_state = reds->inputs_state->mouse_state; ring_add(&reds->agent_state.write_queue, &buf->base.link); write_to_vdi_port(); } static void add_token() { VDIPortState *state = &reds->agent_state; if (++state->num_tokens == REDS_TOKENS_TO_SEND) { reds_send_tokens(); } } typedef struct MainMigrateData { uint32_t version; uint32_t serial; uint32_t ping_id; uint32_t agent_connected; uint32_t client_agent_started; uint32_t num_client_tokens; uint32_t send_tokens; uint32_t read_state; VDIChunkHeader vdi_chunk_header; uint32_t recive_len; uint32_t message_recive_len; uint32_t read_buf_len; uint32_t write_queue_size; } MainMigrateData; #define MAIN_CHANNEL_MIG_DATA_VERSION 1 typedef struct WriteQueueInfo { uint32_t port; uint32_t len; } WriteQueueInfo; typedef struct SendMainMigrateItem { RedsOutItem base; RedDataHeader header; MainMigrateData data; WriteQueueInfo queue_info[REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES]; } SendMainMigrateItem; static void main_channel_send_migrate_data_item(RedsOutItem *in_item, struct iovec* vec_start, int *len) { SendMainMigrateItem *item = (SendMainMigrateItem *)in_item; VDIPortState *state = &reds->agent_state; struct iovec* vec; int buf_index; RingItem *now; vec = vec_start; item->header.serial = ++reds->serial; item->header.type = RED_MIGRATE_DATA; item->header.size = sizeof(item->data); item->header.sub_list = 0; vec[0].iov_base = &item->header; vec[0].iov_len = sizeof(item->header); vec[1].iov_base = &item->data; vec[1].iov_len = sizeof(item->data); vec += 2; *len = 2; item->data.version = MAIN_CHANNEL_MIG_DATA_VERSION; item->data.serial = reds->serial; item->data.ping_id = reds->ping_id; item->data.agent_connected = !!state->plug_ref; item->data.client_agent_started = state->client_agent_started; item->data.num_client_tokens = state->num_client_tokens; item->data.send_tokens = state->send_tokens; item->data.read_state = state->read_state; item->data.vdi_chunk_header = state->vdi_chunk_header; item->data.recive_len = state->recive_len; item->data.message_recive_len = state->message_recive_len; if (state->current_read_buf) { item->data.read_buf_len = state->current_read_buf->len; if ((vec->iov_len = item->data.read_buf_len - item->data.recive_len)) { vec->iov_base = state->current_read_buf->data; item->header.size += vec->iov_len; vec++; (*len)++; } } else { item->data.read_buf_len = 0; } now = &state->write_queue; item->data.write_queue_size = 0; while ((now = ring_prev(&state->write_queue, now))) { item->data.write_queue_size++; } if (!item->data.write_queue_size) { return; } ASSERT(item->data.write_queue_size <= sizeof(item->queue_info) / sizeof(item->queue_info[0])); vec->iov_base = item->queue_info; vec->iov_len = item->data.write_queue_size * sizeof(item->queue_info[0]); item->header.size += vec->iov_len; vec++; (*len)++; buf_index = 0; now = &state->write_queue; while ((now = ring_prev(&state->write_queue, now))) { VDIPortBuf *buf = (VDIPortBuf *)now; item->queue_info[buf_index].port = buf->chunk_header.port; item->queue_info[buf_index++].len = buf->write_len; ASSERT(vec - vec_start < REDS_MAX_SEND_IOVEC); vec->iov_base = buf->now; vec->iov_len = buf->write_len; item->header.size += vec->iov_len; vec++; (*len)++; } } static void main_channelrelease_migrate_data_item(RedsOutItem *in_item) { SendMainMigrateItem *item = (SendMainMigrateItem *)in_item; free(item); } static void main_channel_push_migrate_data_item() { SendMainMigrateItem *item; if (!(item = (SendMainMigrateItem *)malloc(sizeof(*item)))) { PANIC("malloc failed"); } memset(item, 0, sizeof(*item)); ring_item_init(&item->base.link); item->base.prepare = main_channel_send_migrate_data_item; item->base.release = main_channelrelease_migrate_data_item; reds_push_pipe_item((RedsOutItem *)item); } static int main_channel_restore_vdi_read_state(MainMigrateData *data, uint8_t **in_pos, uint8_t *end) { VDIPortState *state = &reds->agent_state; uint8_t *pos = *in_pos; RingItem *ring_item; state->read_state = data->read_state; state->vdi_chunk_header = data->vdi_chunk_header; state->recive_len = data->recive_len; state->message_recive_len = data->message_recive_len; switch (state->read_state) { case VDI_PORT_READ_STATE_READ_HADER: if (data->read_buf_len) { red_printf("unexpected recive buf"); reds_disconnect(); return FALSE; } state->recive_pos = (uint8_t *)(&state->vdi_chunk_header + 1) - state->recive_len; break; case VDI_PORT_READ_STATE_GET_BUFF: if (state->message_recive_len > state->vdi_chunk_header.size) { red_printf("invalid message recive len"); reds_disconnect(); return FALSE; } if (data->read_buf_len) { red_printf("unexpected recive buf"); reds_disconnect(); return FALSE; } break; case VDI_PORT_READ_STATE_READ_DATA: { VDIReadBuf *buff; uint32_t n; if (!data->read_buf_len) { red_printf("read state and read_buf_len == 0"); reds_disconnect(); return FALSE; } if (state->message_recive_len > state->vdi_chunk_header.size) { red_printf("invalid message recive len"); reds_disconnect(); return FALSE; } if (!(ring_item = ring_get_head(&state->read_bufs))) { red_printf("get read buf failed"); reds_disconnect(); return FALSE; } ring_remove(ring_item); buff = state->current_read_buf = (VDIReadBuf *)ring_item; buff->len = data->read_buf_len; n = buff->len - state->recive_len; if (buff->len > RED_AGENT_MAX_DATA_SIZE || n > RED_AGENT_MAX_DATA_SIZE) { red_printf("bad read position"); reds_disconnect(); return FALSE; } memcpy(buff->data, pos, n); pos += n; state->recive_pos = buff->data + n; break; } default: red_printf("invalid read state"); reds_disconnect(); return FALSE; } *in_pos = pos; return TRUE; } static void free_tmp_internal_buf(VDIPortBuf *buf) { free(buf); } static int main_channel_restore_vdi_wqueue(MainMigrateData *data, uint8_t *pos, uint8_t *end) { VDIPortState *state = &reds->agent_state; WriteQueueInfo *inf; WriteQueueInfo *inf_end; RingItem *ring_item; if (!data->write_queue_size) { return TRUE; } inf = (WriteQueueInfo *)pos; inf_end = inf + data->write_queue_size; pos = (uint8_t *)inf_end; if (pos > end) { red_printf("access violation"); reds_disconnect(); return FALSE; } for (; inf < inf_end; inf++) { if (pos + inf->len > end) { red_printf("access violation"); reds_disconnect(); return FALSE; } if (inf->port == VDP_SERVER_PORT) { VDInternalBuf *buf; if (inf->len > sizeof(*buf) - OFFSETOF(VDInternalBuf, header)) { red_printf("bad buffer len"); reds_disconnect(); return FALSE; } if (!(buf = malloc(sizeof(VDInternalBuf)))) { red_printf("no internal buff"); reds_disconnect(); return FALSE; } ring_item_init(&buf->base.link); buf->base.free = free_tmp_internal_buf; buf->base.now = (uint8_t *)&buf->base.chunk_header; buf->base.write_len = inf->len; memcpy(buf->base.now, pos, buf->base.write_len); ring_add(&reds->agent_state.write_queue, &buf->base.link); } else if (inf->port == VDP_CLIENT_PORT) { VDAgentExtBuf *buf; state->num_tokens--; if (inf->len > sizeof(*buf) - OFFSETOF(VDAgentExtBuf, buf)) { red_printf("bad buffer len"); reds_disconnect(); return FALSE; } if (!(ring_item = ring_get_head(&reds->agent_state.external_bufs))) { red_printf("no external buff"); reds_disconnect(); return FALSE; } ring_remove(ring_item); buf = (VDAgentExtBuf *)ring_item; memcpy(&buf->buf, pos, inf->len); buf->base.now = (uint8_t *)buf->buf; buf->base.write_len = inf->len; ring_add(&reds->agent_state.write_queue, &buf->base.link); } else { red_printf("invalid data"); reds_disconnect(); return FALSE; } pos += inf->len; } return TRUE; } static void main_channel_recive_migrate_data(MainMigrateData *data, uint8_t *end) { VDIPortState *state = &reds->agent_state; uint8_t *pos; if (data->version != MAIN_CHANNEL_MIG_DATA_VERSION) { red_printf("version mismatch"); reds_disconnect(); return; } reds->serial = data->serial; reds->ping_id = data->ping_id; state->num_client_tokens = data->num_client_tokens; ASSERT(state->num_client_tokens + data->write_queue_size <= REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES); state->num_tokens = REDS_AGENT_WINDOW_SIZE - state->num_client_tokens; state->send_tokens = data->send_tokens; if (!data->agent_connected) { if (state->plug_ref) { reds_send_agent_connected(); } return; } if (state->plug_ref == INVALID_VD_OBJECT_REF) { reds_send_agent_disconnected(); return; } if (state->plug_generation > 1) { reds_send_agent_disconnected(); reds_send_agent_connected(); return; } state->client_agent_started = data->client_agent_started; pos = (uint8_t *)(data + 1); if (!main_channel_restore_vdi_read_state(data, &pos, end)) { return; } main_channel_restore_vdi_wqueue(data, pos, end); ASSERT(state->num_client_tokens + state->num_tokens == REDS_AGENT_WINDOW_SIZE); } static void reds_main_handle_message(void *opaque, RedDataHeader *message) { switch (message->type) { case REDC_AGENT_START: { RedcAgentTokens *agent_start; red_printf("agent start"); if (!reds->peer) { return; } agent_start = (RedcAgentTokens *)(message + 1); reds->agent_state.client_agent_started = TRUE; reds->agent_state.send_tokens = agent_start->num_tokens; read_from_vdi_port(); break; } case REDC_AGENT_DATA: { RingItem *ring_item; VDAgentExtBuf *buf; if (!reds->agent_state.num_client_tokens) { red_printf("token vailoation"); reds_disconnect(); break; } --reds->agent_state.num_client_tokens; if (!vdagent) { add_token(); break; } if (!reds->agent_state.client_agent_started) { red_printf("REDC_AGENT_DATA race"); add_token(); break; } if (message->size > RED_AGENT_MAX_DATA_SIZE) { red_printf("invalid agent message"); reds_disconnect(); break; } if (!(ring_item = ring_get_head(&reds->agent_state.external_bufs))) { red_printf("no agent free bufs"); reds_disconnect(); break; } ring_remove(ring_item); buf = (VDAgentExtBuf *)ring_item; buf->base.now = (uint8_t *)&buf->base.chunk_header.port; buf->base.write_len = message->size + sizeof(VDIChunkHeader); buf->base.chunk_header.size = message->size; memcpy(buf->buf, message + 1, message->size); ring_add(&reds->agent_state.write_queue, ring_item); write_to_vdi_port(); break; } case REDC_AGENT_TOKEN: { RedcAgentTokens *token; if (!reds->agent_state.client_agent_started) { red_printf("REDC_AGENT_TOKEN race"); break; } token = (RedcAgentTokens *)(message + 1); reds->agent_state.send_tokens += token->num_tokens; read_from_vdi_port(); break; } case REDC_ATTACH_CHANNELS: reds_send_channels(); break; case REDC_MIGRATE_CONNECTED: red_printf("connected"); if (reds->mig_wait_connect) { reds_mig_cleanup(); } break; case REDC_MIGRATE_CONNECT_ERROR: red_printf("mig connect error"); if (reds->mig_wait_connect) { reds_mig_cleanup(); } break; case REDC_MOUSE_MODE_REQUEST: { switch (((RedcMouseModeRequest *)(message + 1))->mode) { case RED_MOUSE_MODE_CLIENT: if (reds->is_client_mouse_allowed) { reds_set_mouse_mode(RED_MOUSE_MODE_CLIENT); } else { red_printf("client mouse is disabled"); } break; case RED_MOUSE_MODE_SERVER: reds_set_mouse_mode(RED_MOUSE_MODE_SERVER); break; default: red_printf("unsupported mouse mode"); } break; } case REDC_PONG: { RedPing *ping = (RedPing *)(message + 1); uint64_t roundtrip; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); roundtrip = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL - ping->timestamp; if (ping->id == reds->net_test_id) { switch (reds->net_test_stage) { case NET_TEST_STAGE_WARMUP: reds->net_test_id++; reds->net_test_stage = NET_TEST_STAGE_LATENCY; break; case NET_TEST_STAGE_LATENCY: reds->net_test_id++; reds->net_test_stage = NET_TEST_STAGE_RATE; letancy = roundtrip; break; case NET_TEST_STAGE_RATE: reds->net_test_id = 0; if (roundtrip <= letancy) { // probably high load on client or server result with incorrect values letancy = 0; red_printf("net test: invalid values, letancy %lu roundtrip %lu. assuming high" "bendwidth", letancy, roundtrip); break; } bitrate_per_sec = (uint64_t)(NET_TEST_BYTES * 8) * 1000000 / (roundtrip - letancy); red_printf("net test: letancy %f ms, bitrate %lu bps (%f Mbps)%s", (double)letancy / 1000, bitrate_per_sec, (double)bitrate_per_sec / 1024 / 1024, IS_LOW_BANDWIDTH() ? " LOW BANDWIDTH" : ""); reds->net_test_stage = NET_TEST_STAGE_INVALID; break; default: red_printf("invalid net test stage, ping id %d test id %d stage %d", ping->id, reds->net_test_id, reds->net_test_stage); } break; } #ifdef RED_STATISTICS reds_update_stat_value(&reds->roundtrip_stat, roundtrip); do_info_rtt_client(); #endif break; } case REDC_MIGRATE_FLUSH_MARK: main_channel_push_migrate_data_item(); break; case REDC_MIGRATE_DATA: main_channel_recive_migrate_data((MainMigrateData *)(message + 1), (uint8_t *)(message + 1) + message->size); reds->mig_target = FALSE; while (write_to_vdi_port() || read_from_vdi_port()); break; case REDC_DISCONNECTING: break; default: red_printf("unexpected type %d", message->type); } } static void reds_main_read(void *data) { if (handle_incoming(reds->peer, &reds->in_handler)) { reds_disconnect(); } } static int reds_send_data() { RedsOutgoingData *outgoing = &reds->outgoing; int n; if (!outgoing->item) { return TRUE; } ASSERT(outgoing->vec_size); for (;;) { if ((n = reds->peer->cb_writev(reds->peer->ctx, outgoing->vec, outgoing->vec_size)) == -1) { switch (errno) { case EAGAIN: core->set_file_handlers(core, reds->peer->socket, reds_main_read, reds_main_write, NULL); return FALSE; case EINTR: break; case EPIPE: reds_disconnect(); return FALSE; default: red_printf("%s", strerror(errno)); reds_disconnect(); return FALSE; } } else { outgoing->vec = reds_iovec_skip(outgoing->vec, n, &outgoing->vec_size); if (!outgoing->vec_size) { outgoing->item->release(outgoing->item); outgoing->item = NULL; outgoing->vec = outgoing->vec_buf; return TRUE; } } } } static void reds_push() { RedsOutgoingData *outgoing = &reds->outgoing; RingItem *item; for (;;) { if (!reds->peer || outgoing->item || !(item = ring_get_tail(&outgoing->pipe))) { return; } ring_remove(item); outgoing->item = (RedsOutItem *)item; outgoing->item->prepare(outgoing->item, outgoing->vec_buf, &outgoing->vec_size); reds_send_data(); } } static void reds_main_write(void *data) { RedsOutgoingData *outgoing = &reds->outgoing; if (reds_send_data()) { reds_push(); if (!outgoing->item) { core->set_file_handlers(core, reds->peer->socket, reds_main_read, NULL, NULL); } } } static int sync_write(RedsStreamContext *peer, void *in_buf, size_t n) { uint8_t *buf = (uint8_t *)in_buf; while (n) { int now = peer->cb_write(peer->ctx, buf, n); if (now <= 0) { if (now == -1 && (errno == EINTR || errno == EAGAIN)) { continue; } return FALSE; } n -= now; buf += now; } return TRUE; } static int reds_send_link_ack(RedLinkInfo *link) { RedLinkHeader header; RedLinkReply ack; Channel *channel; BUF_MEM *bmBuf; BIO *bio; int ret; header.magic = RED_MAGIC; header.size = sizeof(ack); header.major_version = RED_VERSION_MAJOR; header.minor_version = RED_VERSION_MINOR; ack.error = RED_ERR_OK; if ((channel = reds_find_channel(link->link_mess->channel_type, 0))) { ack.num_common_caps = channel->num_common_caps; ack.num_channel_caps = channel->num_caps; header.size += (ack.num_common_caps + ack.num_channel_caps) * sizeof(uint32_t); } else { ack.num_common_caps = 0; ack.num_channel_caps = 0; } ack.caps_offset = sizeof(RedLinkReply); if (!(link->tiTicketing.rsa = RSA_new())) { red_printf("RSA nes failed"); return FALSE; } if (!(bio = BIO_new(BIO_s_mem()))) { red_printf("BIO new failed"); return FALSE; } RSA_generate_key_ex(link->tiTicketing.rsa, RED_TICKET_KEY_PAIR_LENGTH, link->tiTicketing.bn, NULL); link->tiTicketing.rsa_size = RSA_size(link->tiTicketing.rsa); i2d_RSA_PUBKEY_bio(bio, link->tiTicketing.rsa); BIO_get_mem_ptr(bio, &bmBuf); memcpy(ack.pub_key, bmBuf->data, sizeof(ack.pub_key)); ret = sync_write(link->peer, &header, sizeof(header)) && sync_write(link->peer, &ack, sizeof(ack)); if (channel) { ret = ret && sync_write(link->peer, channel->common_caps, channel->num_common_caps * sizeof(uint32_t)) && sync_write(link->peer, channel->caps, channel->num_caps * sizeof(uint32_t)); } BIO_free(bio); return ret; } static int reds_send_link_error(RedLinkInfo *link, uint32_t error) { RedLinkHeader header; RedLinkReply reply; header.magic = RED_MAGIC; header.size = sizeof(reply); header.major_version = RED_VERSION_MAJOR; header.minor_version = RED_VERSION_MINOR; memset(&reply, 0, sizeof(reply)); reply.error = error; return sync_write(link->peer, &header, sizeof(header)) && sync_write(link->peer, &reply, sizeof(reply)); } static void reds_show_new_channel(RedLinkInfo *link) { red_printf("channel %d:%d, connected sucessfully, over %s link", link->link_mess->channel_type, link->link_mess->channel_id, link->peer->ssl == NULL ? "Non Secure" : "Secure"); } static void reds_send_link_result(RedLinkInfo *link, uint32_t error) { sync_write(link->peer, &error, sizeof(error)); } static void reds_start_net_test() { if (!reds->peer || reds->net_test_id) { return; } if (send_ping(NET_TEST_WARMUP_BYTES) && send_ping(0) && send_ping(NET_TEST_BYTES)) { reds->net_test_id = reds->ping_id - 2; reds->net_test_stage = NET_TEST_STAGE_WARMUP; } } static void reds_handle_main_link(RedLinkInfo *link) { uint32_t connection_id; red_printf(""); reds_disconnect(); if (!link->link_mess->connection_id) { reds_send_link_result(link, RED_ERR_OK); while((connection_id = rand()) == 0); reds->agent_state.num_tokens = 0; reds->agent_state.send_tokens = 0; memcpy(&(reds->taTicket), &taTicket, sizeof(reds->taTicket)); reds->mig_target = FALSE; } else { if (link->link_mess->connection_id != reds->link_id) { reds_send_link_result(link, RED_ERR_BAD_CONNECTION_ID); reds_release_link(link); return; } reds_send_link_result(link, RED_ERR_OK); connection_id = link->link_mess->connection_id; reds->mig_target = TRUE; } reds->link_id = connection_id; reds->mig_inprogress = FALSE; reds->mig_wait_connect = FALSE; reds->mig_wait_disconnect = FALSE; reds->peer = link->peer; reds->in_handler.shut = FALSE; if (reds->mig_target) { LOG_MESSAGE(VD_LOG_INFO, "migrate connection"); } else { LOG_MESSAGE(VD_LOG_INFO, "new user connection"); } reds_show_new_channel(link); __reds_release_link(link); if (vdagent) { reds->agent_state.plug_ref = vdagent->plug(vdagent, &reds->agent_state.plug); if (reds->agent_state.plug_ref == INVALID_VD_OBJECT_REF) { PANIC("vdagent plug failed"); } reds->agent_state.plug_generation++; } core->set_file_handlers(core, reds->peer->socket, reds_main_read, NULL, NULL); if (!reds->mig_target) { SimpleOutItem *item; RedInit *init; if (!(item = new_simple_out_item(RED_INIT, sizeof(RedInit)))) { red_printf("alloc item failed"); reds_disconnect(); return; } init = (RedInit *)item->data; init->session_id = connection_id; init->display_channels_hint = red_dispatcher_count(); init->current_mouse_mode = reds->mouse_mode; init->supported_mouse_modes = RED_MOUSE_MODE_SERVER; if (reds->is_client_mouse_allowed) { init->supported_mouse_modes |= RED_MOUSE_MODE_CLIENT; } init->agent_connected = !!vdagent; init->agent_tokens = REDS_AGENT_WINDOW_SIZE; reds->agent_state.num_client_tokens = REDS_AGENT_WINDOW_SIZE; init->multi_media_time = reds_get_mm_time() - MM_TIME_DELTA; init->ram_hint = red_dispatcher_qxl_ram_size(); reds_push_pipe_item(&item->base); reds_start_net_test(); } } #define RED_MOUSE_STATE_TO_LOCAL(state) \ ((state & REDC_LBUTTON_MASK) | \ ((state & REDC_MBUTTON_MASK) << 1) | \ ((state & REDC_RBUTTON_MASK) >> 1)) #define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) \ (((state & REDC_LBUTTON_MASK) ? VD_AGENT_LBUTTON_MASK : 0) | \ ((state & REDC_MBUTTON_MASK) ? VD_AGENT_MBUTTON_MASK : 0) | \ ((state & REDC_RBUTTON_MASK) ? VD_AGENT_RBUTTON_MASK : 0)) static void activate_modifiers_watch() { core->arm_timer(core, reds->key_modifiers_timer, KEY_MODIFIERS_TTL); } static void push_key_scan(uint8_t scan) { if (!keyboard) { return; } keyboard->push_scan_freg(keyboard, scan); } static void inputs_handle_input(void *opaque, RedDataHeader *header) { InputsState *state = (InputsState *)opaque; uint8_t *buf = (uint8_t *)(header + 1); switch (header->type) { case REDC_INPUTS_KEY_DOWN: { RedcKeyDown *key_up = (RedcKeyDown *)buf; if (key_up->code == CAPS_LOCK_SCAN_CODE || key_up->code == NUM_LOCK_SCAN_CODE || key_up->code == SCROLL_LOCK_SCAN_CODE) { activate_modifiers_watch(); } } case REDC_INPUTS_KEY_UP: { RedcKeyDown *key_down = (RedcKeyDown *)buf; uint8_t *now = (uint8_t *)&key_down->code; uint8_t *end = now + sizeof(key_down->code); for (; now < end && *now; now++) { push_key_scan(*now); } break; } case REDC_INPUTS_MOUSE_MOTION: { RedcMouseMotion *mouse_motion = (RedcMouseMotion *)buf; if (++state->motion_count % RED_MOTION_ACK_BUNCH == 0) { RedDataHeader header; header.serial = ++state->serial; header.type = RED_INPUTS_MOUSE_MOTION_ACK; header.size = 0; header.sub_list = 0; if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(RedDataHeader)) != OUTGOING_OK) { red_printf("motion ack failed"); reds_disconnect(); } } if (mouse && reds->mouse_mode == RED_MOUSE_MODE_SERVER) { mouse->moution(mouse, mouse_motion->dx, mouse_motion->dy, 0, RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state)); } break; } case REDC_INPUTS_MOUSE_POSITION: { RedcMousePosition *pos = (RedcMousePosition *)buf; if (++state->motion_count % RED_MOTION_ACK_BUNCH == 0) { RedDataHeader header; header.serial = ++state->serial; header.type = RED_INPUTS_MOUSE_MOTION_ACK; header.size = 0; header.sub_list = 0; if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(RedDataHeader)) != OUTGOING_OK) { red_printf("position ack failed"); reds_disconnect(); } } if (reds->mouse_mode != RED_MOUSE_MODE_CLIENT) { break; } ASSERT((agent_mouse && vdagent) || tablet); if (!agent_mouse || !vdagent) { tablet->position(tablet, pos->x, pos->y, RED_MOUSE_STATE_TO_LOCAL(pos->buttons_state)); break; } VDAgentMouseState *mouse_state = &state->mouse_state; mouse_state->x = pos->x; mouse_state->y = pos->y; mouse_state->buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(pos->buttons_state); mouse_state->display_id = pos->display_id; reds_handle_agent_mouse_event(); break; } case REDC_INPUTS_MOUSE_PRESS: { RedcMousePress *mouse_press = (RedcMousePress *)buf; int dz = 0; if (mouse_press->button == REDC_MOUSE_UBUTTON) { dz = -1; } else if (mouse_press->button == REDC_MOUSE_DBUTTON) { dz = 1; } if (reds->mouse_mode == RED_MOUSE_MODE_CLIENT) { if (agent_mouse && vdagent) { reds->inputs_state->mouse_state.buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_press->buttons_state) | (dz == -1 ? VD_AGENT_UBUTTON_MASK : 0) | (dz == 1 ? VD_AGENT_DBUTTON_MASK : 0); reds_handle_agent_mouse_event(); } else if (tablet) { tablet->wheel(tablet, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state)); } } else if (mouse) { mouse->moution(mouse, 0, 0, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state)); } break; } case REDC_INPUTS_MOUSE_RELEASE: { RedcMouseRelease *mouse_release = (RedcMouseRelease *)buf; if (reds->mouse_mode == RED_MOUSE_MODE_CLIENT) { if (agent_mouse && vdagent) { reds->inputs_state->mouse_state.buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state); reds_handle_agent_mouse_event(); } else if (tablet) { tablet->buttons(tablet, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state)); } } else if (mouse) { mouse->buttons(mouse, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state)); } break; } case REDC_INPUTS_KEY_MODIFAIERS: { RedcKeyModifiers *modifiers = (RedcKeyModifiers *)buf; if (!keyboard) { break; } uint8_t leds = keyboard->get_leds(keyboard); if ((modifiers->modifiers & RED_SCROLL_LOCK_MODIFIER) != (leds & RED_SCROLL_LOCK_MODIFIER)) { push_key_scan(SCROLL_LOCK_SCAN_CODE); push_key_scan(SCROLL_LOCK_SCAN_CODE | 0x80); } if ((modifiers->modifiers & RED_NUM_LOCK_MODIFIER) != (leds & RED_NUM_LOCK_MODIFIER)) { push_key_scan(NUM_LOCK_SCAN_CODE); push_key_scan(NUM_LOCK_SCAN_CODE | 0x80); } if ((modifiers->modifiers & RED_CAPS_LOCK_MODIFIER) != (leds & RED_CAPS_LOCK_MODIFIER)) { push_key_scan(CAPS_LOCK_SCAN_CODE); push_key_scan(CAPS_LOCK_SCAN_CODE | 0x80); } activate_modifiers_watch(); break; } case REDC_DISCONNECTING: break; default: red_printf("unexpected type %d", header->type); } } void reds_set_client_mouse_allowed(int is_client_mouse_allowed, int x_res, int y_res) { reds->monitor_mode.x_res = x_res; reds->monitor_mode.y_res = y_res; reds->dispatcher_allows_client_mouse = is_client_mouse_allowed; reds_update_mouse_mode(); if (reds->is_client_mouse_allowed && tablet) { tablet->set_logical_size(tablet, reds->monitor_mode.x_res, reds->monitor_mode.y_res); } } static void inputs_relase_keys(void) { push_key_scan(0x2a | 0x80); //LSHIFT push_key_scan(0x36 | 0x80); //RSHIFT push_key_scan(0xe0); push_key_scan(0x1d | 0x80); //RCTRL push_key_scan(0x1d | 0x80); //LCTRL push_key_scan(0xe0); push_key_scan(0x38 | 0x80); //RALT push_key_scan(0x38 | 0x80); //LALT } static void inputs_read(void *data) { InputsState *inputs_state = (InputsState *)data; if (handle_incoming(inputs_state->peer, &inputs_state->in_handler)) { inputs_relase_keys(); core->set_file_handlers(core, inputs_state->peer->socket, NULL, NULL, NULL); if (inputs_state->channel) { inputs_state->channel->data = NULL; reds->inputs_state = NULL; } inputs_state->peer->cb_free(inputs_state->peer); free(inputs_state); } } static void inputs_write(void *data) { InputsState *inputs_state = (InputsState *)data; red_printf(""); if (handle_outgoing(inputs_state->peer, &inputs_state->out_handler)) { reds_disconnect(); } } static void inputs_shutdown(Channel *channel) { InputsState *state = (InputsState *)channel->data; if (state) { state->in_handler.shut = TRUE; shutdown(state->peer->socket, SHUT_RDWR); channel->data = NULL; state->channel = NULL; reds->inputs_state = NULL; } } static void inputs_migrate(Channel *channel) { InputsState *state = (InputsState *)channel->data; RedDataHeader header; RedMigrate migrate; red_printf(""); header.serial = ++state->serial; header.type = RED_MIGRATE; header.size = sizeof(migrate); header.sub_list = 0; migrate.flags = 0; if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(header)) != OUTGOING_OK || outgoing_write(state->peer, &state->out_handler, &migrate, sizeof(migrate)) != OUTGOING_OK) { red_printf("write failed"); } } static void inputs_select(void *opaque, int select) { InputsState *inputs_state; red_printf(""); inputs_state = (InputsState *)opaque; if (select) { core->set_file_handlers(core, inputs_state->peer->socket, inputs_read, inputs_write, inputs_state); } else { core->set_file_handlers(core, inputs_state->peer->socket, inputs_read, NULL, inputs_state); } } static void inputs_may_write(void *opaque) { red_printf(""); } static void inputs_link(Channel *channel, RedsStreamContext *peer, int migration, int num_common_caps, uint32_t *common_caps, int num_caps, uint32_t *caps) { InputsState *inputs_state; int delay_val; int flags; red_printf(""); ASSERT(channel->data == NULL); if (!(inputs_state = malloc(sizeof(InputsState)))) { red_printf("alloc input state failed"); close(peer->socket); return; } delay_val = 1; if (setsockopt(peer->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) { red_printf("setsockopt failed, %s", strerror(errno)); } if ((flags = fcntl(peer->socket, F_GETFL)) == -1 || fcntl(peer->socket, F_SETFL, flags | O_ASYNC) == -1) { red_printf("fcntl failed, %s", strerror(errno)); } memset(inputs_state, 0, sizeof(*inputs_state)); inputs_state->peer = peer; inputs_state->end_pos = 0; inputs_state->channel = channel; inputs_state->in_handler.opaque = inputs_state; inputs_state->in_handler.handle_message = inputs_handle_input; inputs_state->out_handler.length = 0; inputs_state->out_handler.opaque = inputs_state; inputs_state->out_handler.select = inputs_select; inputs_state->out_handler.may_write = inputs_may_write; inputs_state->pending_mouse_event = FALSE; channel->data = inputs_state; reds->inputs_state = inputs_state; core->set_file_handlers(core, peer->socket, inputs_read, NULL, inputs_state); RedDataHeader header; RedInputsInit inputs_init; header.serial = ++inputs_state->serial; header.type = RED_INPUTS_INIT; header.size = sizeof(RedInputsInit); header.sub_list = 0; inputs_init.keyboard_modifiers = keyboard ? keyboard->get_leds(keyboard) : 0; if (outgoing_write(inputs_state->peer, &inputs_state->out_handler, &header, sizeof(RedDataHeader)) != OUTGOING_OK || outgoing_write(inputs_state->peer, &inputs_state->out_handler, &inputs_init, sizeof(RedInputsInit)) != OUTGOING_OK) { red_printf("failed to send modifiers state"); reds_disconnect(); } } static void reds_send_keyborad_modifiers(uint8_t modifiers) { Channel *channel = reds_find_channel(RED_CHANNEL_INPUTS, 0); InputsState *state; if (!channel || !(state = (InputsState *)channel->data)) { return; } ASSERT(state->peer); RedDataHeader header; RedKeyModifiers key_modifiers; header.serial = ++state->serial; header.type = RED_INPUTS_KEY_MODIFAIERS; header.size = sizeof(RedKeyModifiers); header.sub_list = 0; key_modifiers.modifiers = modifiers; if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(RedDataHeader)) != OUTGOING_OK || outgoing_write(state->peer, &state->out_handler, &key_modifiers, sizeof(RedKeyModifiers)) != OUTGOING_OK) { red_printf("failed to send modifiers state"); reds_disconnect(); } } static void reds_on_keyborad_leads_change(void *opaque, uint8_t leds) { reds_send_keyborad_modifiers(leds); } static void openssl_init(RedLinkInfo *link) { unsigned long f4 = RSA_F4; link->tiTicketing.bn = BN_new(); if (!link->tiTicketing.bn) { red_error("OpenSSL BIGNUMS alloc failed"); } BN_set_word(link->tiTicketing.bn, f4); } static void inputs_init() { Channel *channel; if (!(channel = malloc(sizeof(Channel)))) { red_error("alloc inputs chanel failed"); } memset(channel, 0, sizeof(Channel)); channel->type = RED_CHANNEL_INPUTS; channel->link = inputs_link; channel->shutdown = inputs_shutdown; channel->migrate = inputs_migrate; reds_register_channel(channel); } static void reds_handle_other_links(RedLinkInfo *link) { Channel *channel; RedsStreamContext *peer; RedLinkMess *link_mess; uint32_t *caps; link_mess = link->link_mess; if (!reds->link_id || reds->link_id != link_mess->connection_id) { reds_send_link_result(link, RED_ERR_BAD_CONNECTION_ID); reds_release_link(link); return; } if (!(channel = reds_find_channel(link_mess->channel_type, link_mess->channel_id))) { reds_send_link_result(link, RED_ERR_CHANNEL_NOT_AVAILABLE); reds_release_link(link); return; } reds_send_link_result(link, RED_ERR_OK); reds_show_new_channel(link); if (link_mess->channel_type == RED_CHANNEL_INPUTS && !link->peer->ssl) { SimpleOutItem *item; RedNotify *notify; char *mess = "keybord channel is unsecure"; const int mess_len = strlen(mess); LOG_MESSAGE(VD_LOG_WARN, "%s", mess); if (!(item = new_simple_out_item(RED_NOTIFY, sizeof(RedNotify) + mess_len + 1))) { red_printf("alloc item failed"); reds_disconnect(); return; } notify = (RedNotify *)item->data; notify->time_stamp = get_time_stamp(); notify->severty = RED_NOTIFY_SEVERITY_WARN; notify->visibilty = RED_NOTIFY_VISIBILITY_HIGH; notify->what = RED_WARN_GENERAL; notify->message_len = mess_len; memcpy(notify->message, mess, mess_len + 1); reds_push_pipe_item(&item->base); } peer = link->peer; link->link_mess = NULL; __reds_release_link(link); caps = (uint32_t *)((uint8_t *)link_mess + link_mess->caps_offset); channel->link(channel, peer, reds->mig_target, link_mess->num_common_caps, link_mess->num_common_caps ? caps : NULL, link_mess->num_channel_caps, link_mess->num_channel_caps ? caps + link_mess->num_common_caps : NULL); free(link_mess); } static void reds_handle_ticket(void *opaque) { RedLinkInfo *link = (RedLinkInfo *)opaque; char password[RED_MAX_PASSWORD_LENGTH]; time_t ltime; //todo: use monotonic time time(<ime); RSA_private_decrypt(link->tiTicketing.rsa_size, link->tiTicketing.encrypted_ticket.encrypted_data, (unsigned char *)password, link->tiTicketing.rsa, RSA_PKCS1_OAEP_PADDING); if (ticketing_enabled) { int expired = !link->link_mess->connection_id && taTicket.expiration_time < ltime; char *actual_sever_pass = link->link_mess->connection_id ? reds->taTicket.password : taTicket.password; if (strlen(actual_sever_pass) == 0) { reds_send_link_result(link, RED_ERR_PERMISSION_DENIED); red_printf("Ticketing is enabled, but no password is set. " "please set a ticket first"); reds_release_link(link); return; } if (expired || strncmp(password, actual_sever_pass, RED_MAX_PASSWORD_LENGTH) != 0) { reds_send_link_result(link, RED_ERR_PERMISSION_DENIED); LOG_MESSAGE(VD_LOG_WARN, "bad connection password or time expired"); reds_release_link(link); return; } } if (link->link_mess->channel_type == RED_CHANNEL_MAIN) { reds_handle_main_link(link); } else { reds_handle_other_links(link); } } static inline void async_read_clear_handlers(AsyncRead *obj) { if (!obj->active_file_handlers) { return; } obj->active_file_handlers = FALSE; core->set_file_handlers(core, obj->peer->socket, NULL, NULL, NULL); } static void async_read_handler(void *data) { AsyncRead *obj = (AsyncRead *)data; for (;;) { int n = obj->end - obj->now; ASSERT(n > 0); if ((n = obj->peer->cb_read(obj->peer->ctx, obj->now, n)) <= 0) { if (n < 0) { switch (errno) { case EAGAIN: if (!obj->active_file_handlers) { obj->active_file_handlers = TRUE; core->set_file_handlers(core, obj->peer->socket, async_read_handler, NULL, obj); } return; case EINTR: break; default: async_read_clear_handlers(obj); obj->error(obj->opaque, errno); return; } } else { async_read_clear_handlers(obj); obj->error(obj->opaque, 0); return; } } else { obj->now += n; if (obj->now == obj->end) { async_read_clear_handlers(obj); obj->done(obj->opaque); return; } } } } static int reds_security_check(RedLinkInfo *link) { ChannelSecurityOptions *security_option = find_channel_security(link->link_mess->channel_type); uint32_t security = security_option ? security_option->options : default_channel_security; return (link->peer->ssl && (security & SPICE_CHANNEL_SECURITY_SSL)) || (!link->peer->ssl && (security & SPICE_CHANNEL_SECURITY_NON)); } static void reds_handle_read_link_done(void *opaque) { RedLinkInfo *link = (RedLinkInfo *)opaque; RedLinkMess *link_mess = link->link_mess; AsyncRead *obj = &link->asyc_read; uint32_t num_caps = link_mess->num_common_caps + link_mess->num_channel_caps; if (num_caps && (num_caps * sizeof(uint32_t) + link_mess->caps_offset > link->link_header.size || link_mess->caps_offset < sizeof(*link_mess))) { reds_send_link_error(link, RED_ERR_INVALID_DATA); reds_release_link(link); return; } if (!reds_security_check(link)) { if (link->peer->ssl) { LOG_MESSAGE(VD_LOG_INFO, "channels of type %d should connect only over " "a non secure link", link_mess->channel_type); red_printf("spice channels %d should not be encrypted", link_mess->channel_type); reds_send_link_error(link, RED_ERR_NEED_UNSECURED); } else { LOG_MESSAGE(VD_LOG_INFO, "channels of type %d should connect only over " "a secure link", link_mess->channel_type); red_printf("spice channels %d should be encrypted", link_mess->channel_type); reds_send_link_error(link, RED_ERR_NEED_SECURED); } reds_release_link(link); return; } if (!reds_send_link_ack(link)) { reds_release_link(link); return; } obj->now = (uint8_t *)&link->tiTicketing.encrypted_ticket.encrypted_data; obj->end = obj->now + link->tiTicketing.rsa_size; obj->done = reds_handle_ticket; async_read_handler(&link->asyc_read); } static void reds_handle_link_error(void *opaque, int err) { RedLinkInfo *link = (RedLinkInfo *)opaque; switch (err) { case 0: case EPIPE: break; default: red_printf("%s", strerror(errno)); break; } reds_release_link(link); } static void reds_handle_read_header_done(void *opaque) { RedLinkInfo *link = (RedLinkInfo *)opaque; RedLinkHeader *header = &link->link_header; AsyncRead *obj = &link->asyc_read; if (header->magic != RED_MAGIC) { reds_send_link_error(link, RED_ERR_INVALID_MAGIC); LOG_MESSAGE(VD_LOG_ERROR, "bad magic %u", header->magic); reds_release_link(link); return; } if (header->major_version != RED_VERSION_MAJOR) { if (header->major_version > 0) { reds_send_link_error(link, RED_ERR_VERSION_MISMATCH); } LOG_MESSAGE(VD_LOG_INFO, "version mismatch client %u.%u server %u.%u", header->major_version, header->minor_version, RED_VERSION_MAJOR, RED_VERSION_MINOR); red_printf("version mismatch"); reds_release_link(link); return; } reds->peer_minor_version = header->minor_version; if (header->size < sizeof(RedLinkMess)) { reds_send_link_error(link, RED_ERR_INVALID_DATA); red_printf("bad size %u", header->size); reds_release_link(link); return; } if (!(link->link_mess = malloc(header->size))) { red_printf("malloc failed %u", header->size); reds_release_link(link); return; } obj->now = (uint8_t *)link->link_mess; obj->end = obj->now + header->size; obj->done = reds_handle_read_link_done; async_read_handler(&link->asyc_read); } static void reds_handle_new_link(RedLinkInfo *link) { AsyncRead *obj = &link->asyc_read; obj->opaque = link; obj->peer = link->peer; obj->now = (uint8_t *)&link->link_header; obj->end = (uint8_t *)((RedLinkHeader *)&link->link_header + 1); obj->active_file_handlers = FALSE; obj->done = reds_handle_read_header_done; obj->error = reds_handle_link_error; async_read_handler(&link->asyc_read); } static void reds_handle_ssl_accept(void *data) { RedLinkInfo *link = (RedLinkInfo *)data; int return_code; if ((return_code = SSL_accept(link->peer->ssl)) != 1) { int ssl_error = SSL_get_error(link->peer->ssl, return_code); if (ssl_error != SSL_ERROR_WANT_READ && ssl_error != SSL_ERROR_WANT_WRITE) { red_printf("SSL_accept failed, error=%d", ssl_error); reds_release_link(link); } return; } reds_handle_new_link(link); } static RedLinkInfo *__reds_accept_connection(int listen_socket) { RedLinkInfo *link; RedsStreamContext *peer; int delay_val = 1; int flags; int socket; if ((socket = accept(listen_socket, NULL, 0)) == -1) { red_printf("accept failed, %s", strerror(errno)); return NULL; } if ((flags = fcntl(socket, F_GETFL)) == -1) { red_printf("accept failed, %s", strerror(errno)); goto error1; } if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) == -1) { red_printf("accept failed, %s", strerror(errno)); goto error1; } if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) { red_printf("setsockopt failed, %s", strerror(errno)); } if (!(link = malloc(sizeof(RedLinkInfo)))) { red_printf("malloc failed"); goto error1; } if (!(peer = malloc(sizeof(RedsStreamContext)))) { red_printf("malloc failed"); goto error2; } memset(link, 0, sizeof(RedLinkInfo)); memset(peer, 0, sizeof(RedsStreamContext)); link->peer = peer; peer->socket = socket; openssl_init(link); return link; error2: free(link); error1: close(socket); return NULL; } static RedLinkInfo *reds_accept_connection(int listen_socket) { RedLinkInfo *link; RedsStreamContext *peer; if (!(link = __reds_accept_connection(listen_socket))) { return NULL; } peer = link->peer; peer->ctx = (void *)((unsigned long)link->peer->socket); peer->cb_read = (int (*)(void *, void *, int))reds_read; peer->cb_write = (int (*)(void *, void *, int))reds_write; peer->cb_readv = (int (*)(void *, const struct iovec *vector, int count))readv; peer->cb_writev = (int (*)(void *, const struct iovec *vector, int count))writev; peer->cb_free = (int (*)(RedsStreamContext *))reds_free; return link; } static void reds_accept_ssl_connection(void *data) { RedLinkInfo *link; int return_code; int ssl_error; BIO *sbio; link = __reds_accept_connection(reds->secure_listen_socket); if (link == NULL) { return; } // Handle SSL handshaking if (!(sbio = BIO_new_socket(link->peer->socket, BIO_NOCLOSE))) { red_printf("could not allocate ssl bio socket"); goto error; } link->peer->ssl = SSL_new(reds->ctx); if (!link->peer->ssl) { red_printf("could not allocate ssl context"); BIO_free(sbio); goto error; } SSL_set_bio(link->peer->ssl, sbio, sbio); link->peer->ctx = (void *)(link->peer->ssl); link->peer->cb_write = (int (*)(void *, void *, int))reds_ssl_write; link->peer->cb_read = (int (*)(void *, void *, int))reds_ssl_read; link->peer->cb_readv = NULL; link->peer->cb_writev = reds_ssl_writev; link->peer->cb_free = (int (*)(RedsStreamContext *))reds_ssl_free; return_code = SSL_accept(link->peer->ssl); if (return_code == 1) { reds_handle_new_link(link); return; } ssl_error = SSL_get_error(link->peer->ssl, return_code); if (return_code == -1 && (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE)) { core->set_file_handlers(core, link->peer->socket, reds_handle_ssl_accept, reds_handle_ssl_accept, link); return; } ERR_print_errors_fp(stderr); red_printf("SSL_accept failed, error=%d", ssl_error); SSL_free(link->peer->ssl); error: close(link->peer->socket); free(link->peer); BN_free(link->tiTicketing.bn); free(link); } static void reds_accept(void *data) { RedLinkInfo *link; link = reds_accept_connection(reds->listen_socket); if (link == NULL) { red_printf("accept failed"); return; } reds_handle_new_link(link); } static int reds_init_socket(const char *addr, int portnr, int family) { static const int on=1, off=0; struct addrinfo ai,*res,*e; char port[33]; char uaddr[INET6_ADDRSTRLEN+1]; char uport[33]; int slisten,rc; memset(&ai,0, sizeof(ai)); ai.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; ai.ai_socktype = SOCK_STREAM; ai.ai_family = family; snprintf(port, sizeof(port), "%d", portnr); rc = getaddrinfo(strlen(addr) ? addr : NULL, port, &ai, &res); if (rc != 0) { red_error("getaddrinfo(%s,%s): %s\n", addr, port, gai_strerror(rc)); } for (e = res; e != NULL; e = e->ai_next) { getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen, uaddr,INET6_ADDRSTRLEN, uport,32, NI_NUMERICHOST | NI_NUMERICSERV); slisten = socket(e->ai_family, e->ai_socktype, e->ai_protocol); if (slisten < 0) { continue; } setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,(void*)&on,sizeof(on)); #ifdef IPV6_V6ONLY if (e->ai_family == PF_INET6) { /* listen on both ipv4 and ipv6 */ setsockopt(slisten,IPPROTO_IPV6,IPV6_V6ONLY,(void*)&off, sizeof(off)); } #endif if (bind(slisten, e->ai_addr, e->ai_addrlen) == 0) { goto listen; } close(slisten); } red_error("%s: binding socket to %s:%d failed\n", __FUNCTION__, addr, portnr); freeaddrinfo(res); return -1; listen: freeaddrinfo(res); if (listen(slisten,1) != 0) { red_error("%s: listen: %s", __FUNCTION__, strerror(errno)); close(slisten); return -1; } return slisten; } static void reds_init_net() { if (spice_port != -1) { reds->listen_socket = reds_init_socket(spice_addr, spice_port, spice_family); if (core->set_file_handlers(core, reds->listen_socket, reds_accept, NULL, NULL)) { red_error("set fd handle failed"); } } if (spice_secure_port != -1) { reds->secure_listen_socket = reds_init_socket(spice_addr, spice_secure_port, spice_family); if (core->set_file_handlers(core, reds->secure_listen_socket, reds_accept_ssl_connection, NULL, NULL)) { red_error("set fd handle failed"); } } } static void load_dh_params(SSL_CTX *ctx, char *file) { DH *ret = 0; BIO *bio; if ((bio = BIO_new_file(file, "r")) == NULL) { red_error("Could not open DH file"); } ret = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); if (ret == 0) { red_error("Could not read DH params"); } BIO_free(bio); if (SSL_CTX_set_tmp_dh(ctx, ret) < 0) { red_error("Could not set DH params"); } } /*The password code is not thread safe*/ static int ssl_password_cb(char *buf, int size, int flags, void *userdata) { char *pass = ssl_parameters.keyfile_password; if (size < strlen(pass) + 1) { return (0); } strcpy(buf, pass); return (strlen(pass)); } static unsigned long pthreads_thread_id(void) { unsigned long ret; ret = (unsigned long)pthread_self(); return (ret); } static void pthreads_locking_callback(int mode, int type, char *file, int line) { if (mode & CRYPTO_LOCK) { pthread_mutex_lock(&(lock_cs[type])); lock_count[type]++; } else { pthread_mutex_unlock(&(lock_cs[type])); } } static void openssl_thread_setup() { int i; lock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long)); for (i = 0; i < CRYPTO_num_locks(); i++) { lock_count[i] = 0; pthread_mutex_init(&(lock_cs[i]), NULL); } CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id); CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback); } static void reds_init_ssl() { #if OPENSSL_VERSION_NUMBER >= 0x10000000L const SSL_METHOD *ssl_method; #else SSL_METHOD *ssl_method; #endif int return_code; long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; /* Global system initialization*/ SSL_library_init(); SSL_load_error_strings(); /* Create our context*/ ssl_method = TLSv1_method(); reds->ctx = SSL_CTX_new(ssl_method); if (!reds->ctx) { red_error("Could not allocate new SSL context"); } /* Limit connection to TLSv1 only */ #ifdef SSL_OP_NO_COMPRESSION ssl_options |= SSL_OP_NO_COMPRESSION; #endif SSL_CTX_set_options(reds->ctx, ssl_options); /* Load our keys and certificates*/ return_code = SSL_CTX_use_certificate_chain_file(reds->ctx, ssl_parameters.certs_file); if (return_code != 1) { red_error("Could not load certificates from %s", ssl_parameters.certs_file); } SSL_CTX_set_default_passwd_cb(reds->ctx, ssl_password_cb); return_code = SSL_CTX_use_PrivateKey_file(reds->ctx, ssl_parameters.private_key_file, SSL_FILETYPE_PEM); if (return_code != 1) { red_error("Could not user private key file"); } /* Load the CAs we trust*/ return_code = SSL_CTX_load_verify_locations(reds->ctx, ssl_parameters.ca_certificate_file, 0); if (return_code != 1) { red_error("Could not use ca file"); } #if (OPENSSL_VERSION_NUMBER < 0x00905100L) SSL_CTX_set_verify_depth(reds->ctx, 1); #endif if (strlen(ssl_parameters.dh_key_file) > 0) { load_dh_params(reds->ctx, ssl_parameters.dh_key_file); } SSL_CTX_set_session_id_context(reds->ctx, (const unsigned char *)"SPICE", 5); if (strlen(ssl_parameters.ciphersuite) > 0) { SSL_CTX_set_cipher_list(reds->ctx, ssl_parameters.ciphersuite); } openssl_thread_setup(); #ifndef SSL_OP_NO_COMPRESSION STACK *cmp_stack = SSL_COMP_get_compression_methods(); sk_zero(cmp_stack); #endif } static void reds_exit() { if (reds->peer) { close(reds->peer->socket); } #ifdef RED_STATISTICS shm_unlink(reds->stat_shm_name); free(reds->stat_shm_name); #endif unsetenv("QEMU_AUDIO_DRV"); } enum { SPICE_OPTION_INVALID, SPICE_OPTION_PORT, SPICE_OPTION_SPORT, SPICE_OPTION_HOST, SPICE_OPTION_IMAGE_COMPRESSION, SPICE_OPTION_PASSWORD, SPICE_OPTION_DISABLE_TICKET, SPICE_OPTION_RENDERER, SPICE_OPTION_SSLKEY, SPICE_OPTION_SSLCERTS, SPICE_OPTION_SSLCAFILE, SPICE_OPTION_SSLDHFILE, SPICE_OPTION_SSLPASSWORD, SPICE_OPTION_SSLCIPHERSUITE, SPICE_SECURED_CHANNELS, SPICE_UNSECURED_CHANNELS, SPICE_OPTION_STREAMING_VIDEO, SPICE_OPTION_AGENT_MOUSE, SPICE_OPTION_PLAYBACK_COMPRESSION, }; typedef struct OptionsMap { const char *name; int val; } OptionsMap; static int find_option(const char *str, OptionsMap *options_map) { int i = 0; for (i = 0; options_map[i].name != NULL; i++) { if (strcmp(str, options_map[i].name) == 0) { return options_map[i].val; } } return SPICE_OPTION_INVALID; } static void clear_blanks(char **ptr) { char *str = *ptr; while (isspace(*str)) { str++; } while (isspace(str[strlen(str) - 1])) { str[strlen(str) - 1] = 0; } *ptr = str; } static int get_option(char **args, char **out_val, OptionsMap *map, char seperator) { char *p; char *next; char *val; ASSERT(args && out_val); p = *args; if ((next = strchr(p, seperator))) { *next = 0; *args = next + 1; } else { *args = NULL; } if ((val = strchr(p, '='))) { *(val++) = 0; clear_blanks(&val); *out_val = (strlen(val) == 0) ? NULL : val; } else { *out_val = NULL; } clear_blanks(&p); return find_option(p, map); } enum { SPICE_TICKET_OPTION_INVALID, SPICE_TICKET_OPTION_EXPIRATION, SPICE_TICKET_OPTION_CONNECTED, }; static OptionsMap _spice_ticket_options[] = { {"expiration", SPICE_TICKET_OPTION_EXPIRATION}, {"connected", SPICE_TICKET_OPTION_CONNECTED}, {NULL, 0}, }; static inline void on_activating_ticketing() { if (!ticketing_enabled && reds->peer) { red_printf("disconnecting"); reds_disconnect(); } } static void reds_reset_ticketing() { on_activating_ticketing(); ticketing_enabled = 1; taTicket.expiration_time = 0; memset(taTicket.password, 0, sizeof(taTicket.password)); } static void reds_set_ticketing(const char *pass, long expiration) { ASSERT(expiration >= 0); on_activating_ticketing(); ticketing_enabled = 1; if (expiration == 0) { taTicket.expiration_time = INT_MAX; } else { time_t ltime; time(<ime); taTicket.expiration_time = ltime + expiration; } strncpy(taTicket.password, pass, sizeof(taTicket.password)); } static void reds_do_set_ticket(const char *password, const char *args) { long expiration = 0; char *local_args = NULL; const char *term_str = "invalid args"; int disconnect = FALSE; int fail = FALSE; if (!password) { term_str = "unexpected NULL password"; goto error; } if (args) { char *in_args; int option; char *val; in_args = local_args = malloc(strlen(args) + 1); strcpy(local_args, args); do { switch (option = get_option(&in_args, &val, _spice_ticket_options, ',')) { case SPICE_TICKET_OPTION_EXPIRATION: { char *endptr; if (!val) { goto error; } expiration = strtol(val, &endptr, 0); if (endptr != val + strlen(val) || expiration < 0) { term_str = "invalid expiration"; goto error; } break; } case SPICE_TICKET_OPTION_CONNECTED: if (!val) { goto error; } if (strcmp(val, "disconnect") == 0) { disconnect = TRUE; fail = FALSE; } else if (strcmp(val, "fail") == 0) { fail = TRUE; disconnect = FALSE; } else if (strcmp(val, "keep") == 0) { fail = FALSE; disconnect = FALSE; } else { goto error; } break; default: goto error; } } while (in_args); } if (fail && reds->peer) { term_str = "Ticket set failed"; } else { if (disconnect) { reds_disconnect(); } reds_set_ticketing(password, expiration); term_str = "Ticket set successfully"; } core->term_printf(core, "%s\n", term_str); free(local_args); return; error: reds_reset_ticketing(); core->term_printf(core, "%s\n", term_str); free(local_args); } static void reds_do_set_ticket_2(const VDICmdArg *args) { const char *arg2 = NULL; if (!args_is_string(args)) { red_printf("invalid args"); return; } if (!args_is_empty(&args[1])) { if (!args_is_string(&args[1])) { red_printf("invalid args"); return; } arg2 = args[1].string_val; } reds_do_set_ticket(args[0].string_val, arg2); } static void reds_do_set_ticket64(const char *password64, const char *args) { char *password; if (!password64) { reds_reset_ticketing(); core->term_printf(core, "unexpected NULL password\n"); return; } if (!(password = base64decode(password64, strlen(password64)))) { reds_reset_ticketing(); core->term_printf(core, "set_ticket64 failed!\n"); return; } reds_do_set_ticket(password, args); free(password); } static void reds_do_set_ticket64_2(const VDICmdArg *args) { const char *arg2 = NULL; if (!args_is_string(args)) { red_printf("invalid args"); return; } if (!args_is_empty(&args[1])) { if (!args_is_string(&args[1])) { red_printf("invalid args"); return; } arg2 = args[1].string_val; } reds_do_set_ticket64(args[0].string_val, arg2); } static void reds_do_info_spice() { core->term_printf(core, "spice info:"); if (reds->peer) { char *ip = NULL; struct sockaddr_in sock_addr; socklen_t len = sizeof(sock_addr); if (getpeername(reds->peer->socket, (struct sockaddr *)&sock_addr, &len) != -1) { ip = inet_ntoa(sock_addr.sin_addr); } core->term_printf(core, " client=%s", ip); } else { core->term_printf(core, " disconnected"); } core->term_printf(core, " ticketing=%s", ticketing_enabled ? "on" : "off"); switch (image_compression) { case SPICE_IMAGE_COMPRESS_AUTO_GLZ: core->term_printf(core, " ic=auto_glz"); break; case SPICE_IMAGE_COMPRESS_AUTO_LZ: core->term_printf(core, " ic=auto_lz"); break; case SPICE_IMAGE_COMPRESS_QUIC: core->term_printf(core, " ic=quic"); break; case SPICE_IMAGE_COMPRESS_LZ: core->term_printf(core, " ic=lz"); break; case SPICE_IMAGE_COMPRESS_GLZ: core->term_printf(core, " ic=glz"); break; case SPICE_IMAGE_COMPRESS_OFF: core->term_printf(core, " ic=off"); break; case SPICE_IMAGE_COMPRESS_INVALID: default: core->term_printf(core, " ic=invalid"); } switch (streaming_video) { case STREAM_VIDEO_ALL: core->term_printf(core, " sv=all"); break; case STREAM_VIDEO_FILTER: core->term_printf(core, " sv=filter"); break; case STREAM_VIDEO_OFF: core->term_printf(core, " sv=off"); break; case STREAM_VIDEO_INVALID: default: core->term_printf(core, " sv=invalid"); } core->term_printf(core, " playback-compression=%s\n", snd_get_playback_compression() ? "on" : "off"); } static void set_image_compression(spice_image_compression_t val) { if (val == image_compression) { return; } image_compression = val; red_dispatcher_on_ic_change(); } static spice_image_compression_t reds_get_image_compression(const char *val) { if ((strcmp(val, "on") == 0) || (strcmp(val, "auto_glz") == 0)) { return SPICE_IMAGE_COMPRESS_AUTO_GLZ; } else if (strcmp(val, "auto_lz") == 0) { return SPICE_IMAGE_COMPRESS_AUTO_LZ; } else if (strcmp(val, "quic") == 0) { return SPICE_IMAGE_COMPRESS_QUIC; } else if (strcmp(val, "glz") == 0) { return SPICE_IMAGE_COMPRESS_GLZ; } else if (strcmp(val, "lz") == 0) { return SPICE_IMAGE_COMPRESS_LZ; } else if (strcmp(val, "off") == 0) { return SPICE_IMAGE_COMPRESS_OFF; } return SPICE_IMAGE_COMPRESS_INVALID; } static void reds_do_set_image_compression(const char *val) { spice_image_compression_t real_val = reds_get_image_compression(val); if (real_val == SPICE_IMAGE_COMPRESS_INVALID) { core->term_printf(core, "bad image compression arg\n"); return; } set_image_compression(real_val); } static void reds_do_set_image_compression_2(const VDICmdArg *args) { if (!args_is_string(args)) { red_printf("invalid args"); return; } reds_do_set_image_compression(args[0].string_val); } static int reds_get_streaming_video(const char *val) { if (strcmp(val, "on") == 0) { return STREAM_VIDEO_FILTER; } else if (strcmp(val, "filter") == 0) { return STREAM_VIDEO_FILTER; } else if (strcmp(val, "all") == 0) { return STREAM_VIDEO_ALL; } else if (strcmp(val, "off") == 0){ return STREAM_VIDEO_OFF; } else { return STREAM_VIDEO_INVALID; } } static void reds_do_set_streaming_video(const char *val) { uint32_t new_val = reds_get_streaming_video(val); if (new_val == STREAM_VIDEO_INVALID) { core->term_printf(core, "bad streaming video arg\n"); return; } if (new_val == streaming_video) { return; } streaming_video = new_val; red_dispatcher_on_sv_change(); } static void reds_do_set_streaming_video_2(const VDICmdArg *args) { if (!args_is_string(args)) { red_printf("invalid args"); return; } reds_do_set_streaming_video(args[0].string_val); } static void reds_do_set_agent_mouse(const char *val) { int new_val; if (strcmp(val, "on") == 0) { new_val = TRUE; } else if (strcmp(val, "off") == 0) { new_val = FALSE; } else { core->term_printf(core, "bad agent mouse arg\n"); return; } if (new_val == agent_mouse) { return; } agent_mouse = new_val; reds_update_mouse_mode(); } static void reds_do_set_agent_mouse_2(const VDICmdArg *args) { if (!args_is_string(args)) { red_printf("invalid args"); return; } reds_do_set_agent_mouse(args[0].string_val); } static void reds_do_set_playback_compression(const char *val) { int on; if (strcmp(val, "on") == 0) { on = TRUE; } else if (strcmp(val, "off") == 0) { on = FALSE; } else { core->term_printf(core, "bad playback compression arg\n"); return; } snd_set_playback_compression(on); } static void reds_do_set_playback_compression_2(const VDICmdArg *args) { if (!args_is_string(args)) { red_printf("invalid args"); return; } reds_do_set_playback_compression(args[0].string_val); } static OptionsMap _spice_options[] = { {"port", SPICE_OPTION_PORT}, {"sport", SPICE_OPTION_SPORT}, {"host", SPICE_OPTION_HOST}, {"ic", SPICE_OPTION_IMAGE_COMPRESSION}, {"password", SPICE_OPTION_PASSWORD}, {"disable-ticketing", SPICE_OPTION_DISABLE_TICKET}, {"renderer", SPICE_OPTION_RENDERER}, {"sslkey", SPICE_OPTION_SSLKEY}, {"sslcert", SPICE_OPTION_SSLCERTS}, {"sslcafile", SPICE_OPTION_SSLCAFILE}, {"ssldhfile", SPICE_OPTION_SSLDHFILE}, {"sslpassword", SPICE_OPTION_SSLPASSWORD}, {"sslciphersuite", SPICE_OPTION_SSLCIPHERSUITE}, {"secure-channels", SPICE_SECURED_CHANNELS}, {"unsecure-channels", SPICE_UNSECURED_CHANNELS}, {"sv", SPICE_OPTION_STREAMING_VIDEO}, {"agent-mouse", SPICE_OPTION_AGENT_MOUSE}, {"playback-compression", SPICE_OPTION_PLAYBACK_COMPRESSION}, {NULL, 0}, }; static OptionsMap _channel_map[] = { {"all", SPICE_CHANNEL_ALL}, {"main", SPICE_CHANNEL_MAIN}, {"display", SPICE_CHANNEL_DISPLAY}, {"inputs", SPICE_CHANNEL_INPUTS}, {"cursor", SPICE_CHANNEL_CURSOR}, {"playback", SPICE_CHANNEL_PLAYBACK}, {"record", SPICE_CHANNEL_RECORD}, {NULL, 0}, }; static void set_all_channels_security(uint32_t security) { while (channels_security) { ChannelSecurityOptions *temp = channels_security; channels_security = channels_security->next; free(temp); } default_channel_security = security; } static void set_one_channel_security(int id, uint32_t security) { ChannelSecurityOptions *security_options; if ((security_options = find_channel_security(id))) { security_options->options = security; return; } security_options = (ChannelSecurityOptions *)malloc(sizeof(*security_options)); if (!security_options) { red_error("malloc failed"); } security_options->channel_id = id; security_options->options = security; security_options->next = channels_security; channels_security = security_options; } static int set_channels_security(const char *channels, uint32_t security) { char *local_str = malloc(strlen(channels) + 1); int channel_name; char *str; char *val; int all = 0; int specific = 0; if (!local_str) { red_error("malloc failed"); } strcpy(local_str, channels); str = local_str; do { switch (channel_name = get_option(&str, &val, _channel_map, '+')) { case SPICE_CHANNEL_ALL: all++; break; case SPICE_CHANNEL_MAIN: specific++; set_one_channel_security(RED_CHANNEL_MAIN, security); break; case SPICE_CHANNEL_DISPLAY: specific++; set_one_channel_security(RED_CHANNEL_DISPLAY, security); break; case SPICE_CHANNEL_INPUTS: specific++; set_one_channel_security(RED_CHANNEL_INPUTS, security); break; case SPICE_CHANNEL_CURSOR: specific++; set_one_channel_security(RED_CHANNEL_CURSOR, security); break; case SPICE_CHANNEL_PLAYBACK: specific++; set_one_channel_security(RED_CHANNEL_PLAYBACK, security); break; case SPICE_CHANNEL_RECORD: specific++; set_one_channel_security(RED_CHANNEL_RECORD, security); break; default: goto error; } if (val) { goto error; } } while (str); if (all) { if (specific || all > 1) { goto error; } set_all_channels_security(security); return TRUE; } return TRUE; error: free(local_str); return FALSE; } int __attribute__ ((visibility ("default"))) spice_parse_args(const char *in_args) { char *local_args; char *args; int option; char *val; int renderers_opt = FALSE; int ssl_port = FALSE; int ssl_key = FALSE; int ssl_certs = FALSE; int ssl_ciphersuite = FALSE; int ssl_cafile = FALSE; int ssl_dhfile = FALSE; memset(&ssl_parameters, 0, sizeof(ssl_parameters)); local_args = malloc(strlen(in_args) + 1); strcpy(local_args, in_args); args = local_args; do { switch (option = get_option(&args, &val, _spice_options, ',')) { case SPICE_OPTION_PORT: { char *endptr; long int port; if (!val) { goto error; } port = strtol(val, &endptr, 0); if (endptr != val + strlen(val) || port < 0 || port > 0xffff) { goto error; } spice_port = port; break; } case SPICE_OPTION_SPORT: { char *endptr; long int port; if (!val) { goto error; } port = strtol(val, &endptr, 0); if (endptr != val + strlen(val) || port < 0 || port > 0xffff) { goto error; } ssl_port = TRUE; spice_secure_port = port; break; } case SPICE_OPTION_HOST: { if (val) { strncpy(spice_addr, val, sizeof(spice_addr)); /* force ipv4 here for backward compatibility */ spice_family = PF_INET; } break; } case SPICE_OPTION_IMAGE_COMPRESSION: if (!val) { goto error; } image_compression = reds_get_image_compression(val); if (image_compression == SPICE_IMAGE_COMPRESS_INVALID) { goto error; } break; case SPICE_OPTION_PASSWORD: ticketing_enabled = 1; if (val) { strncpy(taTicket.password, val, sizeof taTicket.password); //todo: add expiration option taTicket.expiration_time = INT_MAX; } break; case SPICE_OPTION_DISABLE_TICKET: ticketing_enabled = 0; break; case SPICE_OPTION_RENDERER: renderers_opt = TRUE; if (!val) { goto error; } while (val) { char *now = val; if ((val = strchr(now, '+'))) { *val++ = 0; } if (!red_dispatcher_add_renderer(now)) { goto error; } } break; case SPICE_OPTION_SSLCIPHERSUITE: ssl_ciphersuite = TRUE; if (val) { strncpy(ssl_parameters.ciphersuite, val, sizeof(ssl_parameters.ciphersuite)); } break; case SPICE_OPTION_SSLPASSWORD: if (val) { strncpy(ssl_parameters.keyfile_password, val, sizeof(ssl_parameters.keyfile_password)); } break; case SPICE_OPTION_SSLKEY: ssl_key = TRUE; if (val) { strncpy(ssl_parameters.private_key_file, val, sizeof(ssl_parameters.private_key_file)); } break; case SPICE_OPTION_SSLCERTS: ssl_certs = TRUE; if (val) { strncpy(ssl_parameters.certs_file, val, sizeof(ssl_parameters.certs_file)); } break; case SPICE_OPTION_SSLCAFILE: ssl_cafile = TRUE; if (val) { strncpy(ssl_parameters.ca_certificate_file, val, sizeof(ssl_parameters.ca_certificate_file)); } break; case SPICE_OPTION_SSLDHFILE: ssl_dhfile = TRUE; if (val) { strncpy(ssl_parameters.dh_key_file, val, sizeof(ssl_parameters.dh_key_file)); } break; case SPICE_SECURED_CHANNELS: if (!val || !set_channels_security(val, SPICE_CHANNEL_SECURITY_SSL)) { goto error; } break; case SPICE_UNSECURED_CHANNELS: if (!val || !set_channels_security(val, SPICE_CHANNEL_SECURITY_NON)) { goto error; } break; case SPICE_OPTION_STREAMING_VIDEO: if (!val) { goto error; } streaming_video = reds_get_streaming_video(val); if (streaming_video == STREAM_VIDEO_INVALID) { goto error; } break; case SPICE_OPTION_PLAYBACK_COMPRESSION: if (!val) { goto error; } if (strcmp(val, "on") == 0) { snd_set_playback_compression(TRUE); } else if (strcmp(val, "off") == 0) { snd_set_playback_compression(FALSE); } else { goto error; } break; case SPICE_OPTION_AGENT_MOUSE: if (!val) { goto error; } if (strcmp(val, "on") == 0) { agent_mouse = TRUE; } else if (strcmp(val, "off") == 0) { agent_mouse = FALSE; } else { goto error; } break; default: goto error; } } while (args); if (!renderers_opt && !red_dispatcher_add_renderer("cairo")) { goto error; } // All SSL parameters should be either on or off. if (ssl_port != ssl_key || ssl_key != ssl_certs || ssl_certs != ssl_cafile || ssl_cafile != ssl_dhfile || ssl_dhfile != ssl_ciphersuite) { goto error; } free(local_args); return TRUE; error: free(local_args); return FALSE; } const char *spice_usage_str[] __attribute__ ((visibility ("default"))) = { "[port=][,sport=][,host=]", "[,ic=on|auto_glz|auto_lz|quic|glz|lz|off]", "[,playback-compression=on|off]", "[,password=password][,disable-ticketing]", "[,renderer=oglpbuf+oglpixmap+cairo]", "[,sslkeys=key directory,sslcerts=certs directory,sslpassword=pem password,", " sslciphersuite=cipher suite]", "[,secure-channels=all|channel+channel+...]", "[,unsecure-channels=all|channel+channel+...]", "[,vs=on|off] [,ac=on|off]", " listen on interface address port and/or sport ", " setting ticket password using \"ticket\" option", " setting image compression using \"ic\" option [default=auto_local]", " setting playback compression using \"playback-compression\" option [default=on]", " select renderers using \"renderer\" option", " sslkeys - set directory where ssl key file resides.", " sslcerts - set directory where ssl cert file resides.", " sslpassword - set the password to open the private key file.", " sslciphersuite - set the cipher suite to use.", " setting streaming video using \"sv\" option [default=on]", " setting audio compression codec using \"ac\" option [default=off]", " secure-channels - force secure connection on all/specific chnnels.", " channels names: main, inputs, display, cursor,", " playback and record.", " unsecure-channels - force unsecure connection on all/specific chnnels.", " channels names as in secure-channels.", NULL, }; #define REDS_SAVE_VERSION 1 static OptionsMap spice_mig_options[] = { {"spicesport", SPICE_OPTION_SPORT}, {"spiceport", SPICE_OPTION_PORT}, {"spicehost", SPICE_OPTION_HOST}, {NULL, 0}, }; struct RedsMigSpice; typedef struct RedsMigRead { uint8_t buf[RECIVE_BUF_SIZE]; uint32_t end_pos; uint32_t size; void (*handle_data)(struct RedsMigSpice *message); } RedsMigRead; typedef struct RedsMigWrite { uint8_t buf[SEND_BUF_SIZE]; uint8_t *now; uint32_t length; void (*handle_done)(struct RedsMigSpice *s); } RedsMigWrite; typedef struct RedsMigSpice { int fd; RedsMigWrite write; RedsMigRead read; char pub_key[RED_TICKET_PUBKEY_BYTES]; uint32_t mig_key; char *local_args; char *host; int port; int sport; uint16_t cert_pub_key_type; uint32_t cert_pub_key_len; uint8_t* cert_pub_key; } RedsMigSpice; typedef struct RedsMigSpiceMessage { uint32_t link_id; } RedsMigSpiceMessage; typedef struct RedsMigCertPubKeyInfo { uint16_t type; uint32_t len; } RedsMigCertPubKeyInfo; static int reds_mig_actual_read(RedsMigSpice *s) { for (;;) { uint8_t *buf = s->read.buf; uint32_t pos = s->read.end_pos; int n; n = read(s->fd, buf + pos, s->read.size - pos); if (n <= 0) { if (n == 0) { return -1; } switch (errno) { case EAGAIN: return 0; case EINTR: break; case EPIPE: return -1; default: red_printf("%s", strerror(errno)); return -1; } } else { s->read.end_pos += n; if (s->read.end_pos == s->read.size) { s->read.handle_data(s); return 0; } } } } static int reds_mig_actual_write(RedsMigSpice *s) { if (!s->write.length) { return 0; } while (s->write.length) { int n; n = write(s->fd, s->write.now, s->write.length); if (n <= 0) { if (n == 0) { return -1; } switch (errno) { case EAGAIN: return 0; case EINTR: break; case EPIPE: return -1; default: red_printf("%s", strerror(errno)); return -1; } } else { s->write.now += n; s->write.length -= n; } } s->write.handle_done(s); return 0; } static void reds_mig_failed(RedsMigSpice *s) { red_printf(""); core->set_file_handlers(core, s->fd, NULL, NULL, NULL); if (s->local_args) { free(s->local_args); } free(s); reds_mig_disconnect(); } static void reds_mig_write(void *data) { RedsMigSpice *s = data; if (reds_mig_actual_write((RedsMigSpice *)data)) { red_printf("write error cannot continue spice migration"); reds_mig_failed(s); } } static void reds_mig_read(void *data) { RedsMigSpice *s = data; if (reds_mig_actual_read((RedsMigSpice *)data)) { red_printf("read error cannot continue spice migration"); reds_mig_failed(s); } } static void reds_mig_continue(RedsMigSpice *s) { RedMigrationBegin *migrate; SimpleOutItem *item; int host_len; red_printf(""); core->set_file_handlers(core, s->fd, NULL, NULL, NULL); host_len = strlen(s->host) + 1; item = new_simple_out_item(RED_MIGRATE_BEGIN, sizeof(RedMigrationBegin) + host_len + s->cert_pub_key_len); if (!(item)) { red_printf("alloc item failed"); reds_disconnect(); return; } migrate = (RedMigrationBegin *)item->data; migrate->port = s->port; migrate->sport = s->sport; migrate->host_offset = sizeof(RedMigrationBegin); migrate->host_size = host_len; migrate->pub_key_type = s->cert_pub_key_type; migrate->pub_key_offset = sizeof(RedMigrationBegin) + host_len; migrate->pub_key_size = s->cert_pub_key_len; memcpy((uint8_t*)(migrate) + migrate->host_offset , s->host, host_len); memcpy((uint8_t*)(migrate) + migrate->pub_key_offset, s->cert_pub_key, s->cert_pub_key_len); reds_push_pipe_item(&item->base); free(s->local_args); free(s); reds->mig_wait_connect = TRUE; core->arm_timer(core, reds->mig_timer, MIGRATE_TIMEOUT); } static void reds_mig_receive_ack(RedsMigSpice *s) { s->read.size = sizeof(uint32_t); s->read.end_pos = 0; s->read.handle_data = reds_mig_continue; core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s); } static void reds_mig_send_link_id(RedsMigSpice *s) { RedsMigSpiceMessage *data = (RedsMigSpiceMessage *)s->write.buf; memcpy(&data->link_id, &reds->link_id, sizeof(reds->link_id)); s->write.now = s->write.buf; s->write.length = sizeof(RedsMigSpiceMessage); s->write.handle_done = reds_mig_receive_ack; core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s); } static void reds_mig_send_ticket(RedsMigSpice *s) { EVP_PKEY *pubkey = NULL; BIO *bio_key; RSA *rsa; int rsa_size = 0; red_printf(""); bio_key = BIO_new(BIO_s_mem()); if (bio_key != NULL) { BIO_write(bio_key, s->read.buf, RED_TICKET_PUBKEY_BYTES); pubkey = d2i_PUBKEY_bio(bio_key, NULL); rsa = pubkey->pkey.rsa; rsa_size = RSA_size(rsa); if (RSA_public_encrypt(strlen(reds->taTicket.password) + 1, (unsigned char *)reds->taTicket.password, (uint8_t *)(s->write.buf), rsa, RSA_PKCS1_OAEP_PADDING) > 0) { s->write.length = RSA_size(rsa); s->write.now = s->write.buf; s->write.handle_done = reds_mig_send_link_id; core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s); } else { reds_mig_failed(s); } } else { reds_mig_failed(s); } EVP_PKEY_free(pubkey); BIO_free(bio_key); } static void reds_mig_receive_cert_public_key(RedsMigSpice *s) { s->cert_pub_key = malloc(s->cert_pub_key_len); if (!s->cert_pub_key) { red_printf("alloc failed"); reds_mig_failed(s); return; } memcpy(s->cert_pub_key, s->read.buf, s->cert_pub_key_len); s->read.size = RED_TICKET_PUBKEY_BYTES; s->read.end_pos = 0; s->read.handle_data = reds_mig_send_ticket; core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s); } static void reds_mig_receive_cert_public_key_info(RedsMigSpice *s) { RedsMigCertPubKeyInfo* pubkey_info = (RedsMigCertPubKeyInfo*)s->read.buf; s->cert_pub_key_type = pubkey_info->type; s->cert_pub_key_len = pubkey_info->len; if (s->cert_pub_key_len > RECIVE_BUF_SIZE) { red_printf("certificate public key length exceeds buffer size"); reds_mig_failed(s); return; } if (s->cert_pub_key_len) { s->read.size = s->cert_pub_key_len; s->read.end_pos = 0; s->read.handle_data = reds_mig_receive_cert_public_key; } else { s->cert_pub_key = NULL; s->read.size = RED_TICKET_PUBKEY_BYTES; s->read.end_pos = 0; s->read.handle_data = reds_mig_send_ticket; } core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s); } static void reds_mig_handle_send_abort_done(RedsMigSpice *s) { reds_mig_failed(s); } static void reds_mig_receive_version(RedsMigSpice *s) { uint32_t* dest_version; uint32_t resault; dest_version = (uint32_t*)s->read.buf; resault = REDS_MIG_ABORT; memcpy(s->write.buf, &resault, sizeof(resault)); s->write.length = sizeof(resault); s->write.now = s->write.buf; s->write.handle_done = reds_mig_handle_send_abort_done; core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s); } static void reds_mig_control(RedsMigSpice *spice_migration) { uint32_t *control; core->set_file_handlers(core, spice_migration->fd, NULL, NULL, NULL); control = (uint32_t *)spice_migration->read.buf; switch (*control) { case REDS_MIG_CONTINUE: spice_migration->read.size = sizeof(RedsMigCertPubKeyInfo); spice_migration->read.end_pos = 0; spice_migration->read.handle_data = reds_mig_receive_cert_public_key_info; core->set_file_handlers(core, spice_migration->fd, reds_mig_read, NULL, spice_migration); break; case REDS_MIG_ABORT: red_printf("abort"); reds_mig_failed(spice_migration); break; case REDS_MIG_DIFF_VERSION: red_printf("different versions"); spice_migration->read.size = sizeof(uint32_t); spice_migration->read.end_pos = 0; spice_migration->read.handle_data = reds_mig_receive_version; core->set_file_handlers(core, spice_migration->fd, reds_mig_read, NULL, spice_migration); break; default: red_printf("invalid control"); reds_mig_failed(spice_migration); } } static void reds_mig_receive_control(RedsMigSpice *spice_migration) { spice_migration->read.size = sizeof(uint32_t); spice_migration->read.end_pos = 0; spice_migration->read.handle_data = reds_mig_control; core->set_file_handlers(core, spice_migration->fd, reds_mig_read, NULL, spice_migration); } static void reds_mig_started(void *opaque, const char *in_args) { RedsMigSpice *spice_migration = NULL; uint32_t *version; char *val; char *args; int option; ASSERT(in_args); red_printf(""); reds->mig_inprogress = TRUE; if (reds->listen_socket != -1) { core->set_file_handlers(core, reds->listen_socket, NULL, NULL, NULL); } if (reds->secure_listen_socket != -1) { core->set_file_handlers(core, reds->secure_listen_socket, NULL, NULL, NULL); } if (reds->peer == NULL) { red_printf("not connected to peer"); goto error; } if ((RED_VERSION_MAJOR == 1) && (reds->peer_minor_version < 1)) { red_printf("minor version mismatch client %u server %u", reds->peer_minor_version, RED_VERSION_MINOR); goto error; } spice_migration = (RedsMigSpice *)malloc(sizeof(RedsMigSpice)); if (!spice_migration) { red_printf("Could not allocate memory for spice migration structure"); goto error; } memset(spice_migration, 0, sizeof(RedsMigSpice)); spice_migration->port = -1; spice_migration->sport = -1; if (!(spice_migration->local_args = malloc(strlen(in_args) + 1))) { red_printf("str malloc failed"); goto error; } strcpy(spice_migration->local_args, in_args); args = spice_migration->local_args; do { switch (option = get_option(&args, &val, spice_mig_options, ',')) { case SPICE_OPTION_SPORT: { char *endptr; if (!val) { goto error; } spice_migration->sport = strtol(val, &endptr, 0); if (endptr != val + strlen(val) || spice_migration->sport < 0 || spice_migration->sport > 0xffff) { goto error; } break; } case SPICE_OPTION_PORT: { char *endptr; if (!val) { goto error; } spice_migration->port = strtol(val, &endptr, 0); if ( endptr != val + strlen(val) || spice_migration->port < 0 || spice_migration->port > 0xffff ) { goto error; } break; } case SPICE_OPTION_HOST: if (!val) { goto error; } spice_migration->host = val; break; } } while (args); if ((spice_migration->sport == -1 && spice_migration->port == -1) || !spice_migration->host) { red_printf("invalid args port %d sport %d host %s", spice_migration->port, spice_migration->sport, (spice_migration->host) ? spice_migration->host : "NULL"); goto error; } spice_migration->fd = mig->begin_hook(mig, reds->mig_notifier); if (spice_migration->fd == -1) { goto error; } spice_migration->write.now = spice_migration->write.buf; spice_migration->write.length = sizeof(uint32_t); version = (uint32_t *)spice_migration->write.buf; *version = REDS_MIG_VERSION; spice_migration->write.handle_done = reds_mig_receive_control; core->set_file_handlers(core, spice_migration->fd, reds_mig_write, reds_mig_write, spice_migration); return; error: if (spice_migration) { if (spice_migration->local_args) { free(spice_migration->local_args); } free(spice_migration); } reds_mig_disconnect(); } static void reds_mig_finished(void *opaque, int completed) { SimpleOutItem *item; red_printf(""); if (reds->listen_socket != -1) { core->set_file_handlers(core, reds->listen_socket, reds_accept, NULL, NULL); } if (reds->secure_listen_socket != -1) { core->set_file_handlers(core, reds->secure_listen_socket, reds_accept_ssl_connection, NULL, NULL); } if (reds->peer == NULL) { red_printf("no peer connected"); mig->notifier_done(mig, reds->mig_notifier); return; } reds->mig_inprogress = TRUE; if (completed) { Channel *channel; RedMigrate *migrate; reds->mig_wait_disconnect = TRUE; core->arm_timer(core, reds->mig_timer, MIGRATE_TIMEOUT); if (!(item = new_simple_out_item(RED_MIGRATE, sizeof(RedMigrate)))) { red_printf("alloc item failed"); reds_disconnect(); return; } migrate = (RedMigrate *)item->data; migrate->flags = RED_MIGRATE_NEED_FLUSH | RED_MIGRATE_NEED_DATA_TRANSFER; reds_push_pipe_item(&item->base); channel = reds->channels; while (channel) { channel->migrate(channel); channel = channel->next; } } else { if (!(item = new_simple_out_item(RED_MIGRATE_CANCEL, 0))) { red_printf("alloc item failed"); reds_disconnect(); return; } reds_push_pipe_item(&item->base); reds_mig_cleanup(); } } static int write_all(int fd, const void *in_buf, int len1) { int ret, len; uint8_t *buf = (uint8_t *)in_buf; len = len1; while (len > 0) { ret = write(fd, buf, len); if (ret < 0) { if (errno != EINTR && errno != EAGAIN) { return -1; } } else if (ret == 0) { break; } else { buf += ret; len -= ret; } } return len1 - len; } static int read_all(int fd, void *in_nuf, int lenl) { int ret, len; uint8_t *buf = in_nuf; len = lenl; while (len > 0) { ret = read(fd, buf, len); if (ret < 0) { if (errno != EINTR && errno != EAGAIN) { return -1; } } else if (ret == 0) { break; } else { buf += ret; len -= ret; } } return lenl - len; } static void reds_mig_read_all(int fd, void *buf, int len, const char *name) { int n = read_all(fd, buf, len); if (n != len) { red_error("read %s failed, n=%d (%s)", name, n, strerror(errno)); } } static void reds_mig_write_all(int fd, void *buf, int len, const char *name) { int n = write_all(fd, buf, len); if (n != len) { red_error("write %s faile, n=%d (%s)", name, n, strerror(errno)); } } static void reds_mig_send_cert_public_key(int fd) { FILE* cert_file; X509* x509; EVP_PKEY* pub_key; unsigned char* pp = NULL; int length; BIO* mem_bio; RedsMigCertPubKeyInfo pub_key_info_msg; if (spice_secure_port == -1) { pub_key_info_msg.type = RED_PUBKEY_TYPE_INVALID; pub_key_info_msg.len = 0; reds_mig_write_all(fd, &pub_key_info_msg, sizeof(pub_key_info_msg), "cert public key info"); return; } cert_file = fopen(ssl_parameters.certs_file, "r"); if (!cert_file) { red_error("opening certificate failed"); } x509 = PEM_read_X509_AUX(cert_file, NULL, NULL, NULL); if (!x509) { red_error("reading x509 cert failed"); } pub_key = X509_get_pubkey(x509); if (!pub_key) { red_error("reading public key failed"); } mem_bio = BIO_new(BIO_s_mem()); i2d_PUBKEY_bio(mem_bio, pub_key); if (BIO_flush(mem_bio) != 1) { red_error("bio flush failed"); } length = BIO_get_mem_data(mem_bio, &pp); switch(pub_key->type) { case EVP_PKEY_RSA: pub_key_info_msg.type = RED_PUBKEY_TYPE_RSA; break; case EVP_PKEY_RSA2: pub_key_info_msg.type = RED_PUBKEY_TYPE_RSA2; break; case EVP_PKEY_DSA: pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA; break; case EVP_PKEY_DSA1: pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA1; break; case EVP_PKEY_DSA2: pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA2; break; case EVP_PKEY_DSA3: pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA3; break; case EVP_PKEY_DSA4: pub_key_info_msg.type = RED_PUBKEY_TYPE_DSA4; break; case EVP_PKEY_DH: pub_key_info_msg.type = RED_PUBKEY_TYPE_DH; break; case EVP_PKEY_EC: pub_key_info_msg.type = RED_PUBKEY_TYPE_EC; break; default: red_error("invalid public key type"); } pub_key_info_msg.len = length; reds_mig_write_all(fd, &pub_key_info_msg, sizeof(pub_key_info_msg), "cert public key info"); reds_mig_write_all(fd, pp, length, "cert public key"); BIO_free(mem_bio); fclose(cert_file); EVP_PKEY_free(pub_key); X509_free(x509); } static void reds_mig_recv(void *opaque, int fd) { uint32_t ack_message = *(uint32_t *)"ack_"; char password[RED_MAX_PASSWORD_LENGTH]; RedsMigSpiceMessage mig_message; unsigned long f4 = RSA_F4; TicketInfo ticketing_info; uint32_t version; uint32_t resault; BIO *bio; BUF_MEM *buff; reds_mig_read_all(fd, &version, sizeof(version), "version"); // starting from version 3, if the version of the src is bigger // than ours, we send our version to the src. if (version < REDS_MIG_VERSION) { resault = REDS_MIG_ABORT; reds_mig_write_all(fd, &resault, sizeof(resault), "resault"); mig->notifier_done(mig, reds->mig_notifier); return; } else if (version > REDS_MIG_VERSION) { uint32_t src_resault; uint32_t self_version = REDS_MIG_VERSION; resault = REDS_MIG_DIFF_VERSION; reds_mig_write_all(fd, &resault, sizeof(resault), "resault"); reds_mig_write_all(fd, &self_version, sizeof(self_version), "dest-version"); reds_mig_read_all(fd, &src_resault, sizeof(src_resault), "src resault"); if (src_resault == REDS_MIG_ABORT) { red_printf("abort (response to REDS_MIG_DIFF_VERSION)"); mig->notifier_done(mig, reds->mig_notifier); return; } else if (src_resault != REDS_MIG_CONTINUE) { red_printf("invalid response to REDS_MIG_DIFF_VERSION"); mig->notifier_done(mig, reds->mig_notifier); return; } } else { resault = REDS_MIG_CONTINUE; reds_mig_write_all(fd, &resault, sizeof(resault), "resault"); } reds_mig_send_cert_public_key(fd); ticketing_info.bn = BN_new(); if (!ticketing_info.bn) { red_error("OpenSSL BIGNUMS alloc failed"); } BN_set_word(ticketing_info.bn, f4); if (!(ticketing_info.rsa = RSA_new())) { red_error("OpenSSL RSA alloc failed"); } RSA_generate_key_ex(ticketing_info.rsa, RED_TICKET_KEY_PAIR_LENGTH, ticketing_info.bn, NULL); ticketing_info.rsa_size = RSA_size(ticketing_info.rsa); if (!(bio = BIO_new(BIO_s_mem()))) { red_error("OpenSSL BIO alloc failed"); } i2d_RSA_PUBKEY_bio(bio, ticketing_info.rsa); BIO_get_mem_ptr(bio, &buff); reds_mig_write_all(fd, buff->data, RED_TICKET_PUBKEY_BYTES, "publick key"); reds_mig_read_all(fd, ticketing_info.encrypted_ticket.encrypted_data, ticketing_info.rsa_size, "ticket"); RSA_private_decrypt(ticketing_info.rsa_size, ticketing_info.encrypted_ticket.encrypted_data, (unsigned char *)password, ticketing_info.rsa, RSA_PKCS1_OAEP_PADDING); BN_free(ticketing_info.bn); BIO_free(bio); RSA_free(ticketing_info.rsa); memcpy(reds->taTicket.password, password, sizeof(reds->taTicket.password)); reds_mig_read_all(fd, &mig_message, sizeof(mig_message), "mig data"); reds->link_id = mig_message.link_id; reds_mig_write_all(fd, &ack_message, sizeof(uint32_t), "ack"); mig->notifier_done(mig, reds->mig_notifier); } static void migrate_timout(void *opaque) { red_printf(""); ASSERT(reds->mig_wait_connect || reds->mig_wait_disconnect); reds_mig_disconnect(); } static void key_modifiers_sender(void *opaque) { reds_send_keyborad_modifiers(keyboard ? keyboard->get_leds(keyboard) : 0); } uint32_t reds_get_mm_time() { struct timespec time_space; clock_gettime(CLOCK_MONOTONIC, &time_space); return time_space.tv_sec * 1000 + time_space.tv_nsec / 1000 / 1000; } void reds_update_mm_timer(uint32_t mm_time) { red_dispatcher_set_mm_time(mm_time); } void reds_enable_mm_timer() { RedMultiMediaTime *time_mes; SimpleOutItem *item; core->arm_timer(core, reds->mm_timer, MM_TIMER_GRANULARITY_MS); if (!reds->peer) { return; } if (!(item = new_simple_out_item(RED_MULTI_MEDIA_TIME, sizeof(RedMultiMediaTime)))) { red_printf("alloc item failed"); reds_disconnect(); return; } time_mes = (RedMultiMediaTime *)item->data; time_mes->time = reds_get_mm_time() - MM_TIME_DELTA; reds_push_pipe_item(&item->base); } void reds_desable_mm_timer() { core->disarm_timer(core, reds->mm_timer); } static void mm_timer_proc(void *opaque) { red_dispatcher_set_mm_time(reds_get_mm_time()); core->arm_timer(core, reds->mm_timer, MM_TIMER_GRANULARITY_MS); } static void add_monitor_action_commands(QTermInterface *mon) { mon->add_action_command_handler(mon, "spice", "set_image_compression", "s", reds_do_set_image_compression, "", "<[on|auto_glz|auto_lz|quic|glz|lz|off]>"); mon->add_action_command_handler(mon, "spice", "set_streaming_video", "s", reds_do_set_streaming_video, "", ""); mon->add_action_command_handler(mon, "spice", "set_playback_compression", "s", reds_do_set_playback_compression, "", ""); mon->add_action_command_handler(mon, "spice", "set_ticket", "ss?", reds_do_set_ticket, " [expiration=]" "[,connected=keep|disconnect|fail]", "set the spice connection ticket"); mon->add_action_command_handler(mon, "spice", "set_ticket64", "ss?", reds_do_set_ticket64, " [expiration=]" "[,connected=keep|disconnect|fail]", "set the spice connection ticket"); mon->add_action_command_handler(mon, "spice", "disable_ticketing", "", reds_do_disable_ticketing, "", "entirely disables OTP"); mon->add_action_command_handler(mon, "spice", "set_agent_mouse", "s", reds_do_set_agent_mouse, "", ""); #ifdef RED_STATISTICS mon->add_action_command_handler(mon, "spice", "reset_stat", "", do_reset_statistics, "", "reset spice statistics"); mon->add_action_command_handler(mon, "spice", "ping_client", "s?i?", do_ping_client, "[on [interval]|off]", "ping spice client to measure roundtrip"); #endif } static void add_monitor_action_commands_2(QTerm2Interface *mon) { VDIArgDescriptor s[] = { { "arg1", ARG_TYPE_STRING, FALSE}, { NULL, 0, 0}, }; VDIArgDescriptor empty[] = { { NULL, 0, 0} }; VDIArgDescriptor s_s_o[] = { { "arg1", ARG_TYPE_STRING, FALSE}, { "arg2", ARG_TYPE_STRING, TRUE}, { NULL, 0, 0} }; VDIArgDescriptor s_o_i_o[] = { { "arg1", ARG_TYPE_STRING, TRUE}, { "arg2", ARG_TYPE_INT, TRUE}, { NULL, 0, 0} }; mon->add_action_command_handler(mon, "spice", "set_image_compression", s, reds_do_set_image_compression_2, "<[on|auto_glz|auto_lz|quic|glz|lz|off]>", ""); mon->add_action_command_handler(mon, "spice", "set_streaming_video", s, reds_do_set_streaming_video_2, "", ""); mon->add_action_command_handler(mon, "spice", "set_playback_compression", s, reds_do_set_playback_compression_2, "", ""); mon->add_action_command_handler(mon, "spice", "set_ticket", s_s_o, reds_do_set_ticket_2, " [expiration=]" "[,connected=keep|disconnect|fail]", "set the spice connection ticket"); mon->add_action_command_handler(mon, "spice", "set_ticket64", s_s_o, reds_do_set_ticket64_2, " [expiration=]" "[,connected=keep|disconnect|fail]", "set the spice connection ticket"); mon->add_action_command_handler(mon, "spice", "disable_ticketing", empty, reds_do_disable_ticketing_2, "", "entirely disables OTP"); mon->add_action_command_handler(mon, "spice", "set_agent_mouse", s, reds_do_set_agent_mouse_2, "", ""); #ifdef RED_STATISTICS mon->add_action_command_handler(mon, "spice", "reset_stat", empty, do_reset_statistics_2, "", "reset spice statistics"); mon->add_action_command_handler(mon, "spice", "ping_client", s_o_i_o, do_ping_client_2, "[on [interval]|off]", "ping spice client to measure roundtrip"); #endif } static void add_monitor_info_commands(QTermInterface *mon) { mon->add_info_command_handler(mon, "spice", "state", reds_do_info_spice, "show spice state"); mon->add_info_command_handler(mon, "spice", "ticket", reds_do_info_ticket, "show ticket"); #ifdef RED_STATISTICS mon->add_info_command_handler(mon, "spice", "stat", do_info_statistics, "show spice statistics"); mon->add_info_command_handler(mon, "spice", "rtt_client", do_info_rtt_client, "show rtt to spice client"); #endif } static void add_monitor_info_commands_2(QTerm2Interface *mon) { mon->add_info_command_handler(mon, "spice", "state", reds_do_info_spice, "show spice state"); mon->add_info_command_handler(mon, "spice", "ticket", reds_do_info_ticket, "show ticket"); #ifdef RED_STATISTICS mon->add_info_command_handler(mon, "spice", "stat", do_info_statistics, "show spice statistics"); mon->add_info_command_handler(mon, "spice", "rtt_client", do_info_rtt_client, "show rtt to spice client"); #endif } static void attach_to_red_agent(VDIPortInterface *interface) { VDIPortState *state = &reds->agent_state; vdagent = interface; reds_update_mouse_mode(); if (!reds->peer) { return; } state->plug_ref = vdagent->plug(vdagent, &state->plug); reds->agent_state.plug_generation++; if (reds->mig_target) { return; } reds_send_agent_connected(); } static void interface_change_notifier(void *opaque, VDInterface *interface, VDInterfaceChangeType change) { if (interface->base_version != VM_INTERFACE_VERSION) { red_printf("unsuported base interface version"); return; } switch (change) { case VD_INTERFACE_ADDING: if (strcmp(interface->type, VD_INTERFACE_KEYBOARD) == 0) { red_printf("VD_INTERFACE_KEYBOARD"); if (keyboard) { red_printf("already have keyboard"); return; } if (interface->major_version != VD_INTERFACE_KEYBOARD_MAJOR || interface->minor_version < VD_INTERFACE_KEYBOARD_MINOR) { red_printf("unsuported keyboard interface"); return; } keyboard = (KeyboardInterface *)interface; if (keyboard->register_leds_notifier) { if (!keyboard->register_leds_notifier(keyboard, reds_on_keyborad_leads_change, NULL)) { red_error("register leds notifier failed"); } } } else if (strcmp(interface->type, VD_INTERFACE_MOUSE) == 0) { red_printf("VD_INTERFACE_MOUSE"); if (mouse) { red_printf("already have mouse"); return; } if (interface->major_version != VD_INTERFACE_MOUSE_MAJOR || interface->minor_version < VD_INTERFACE_MOUSE_MINOR) { red_printf("unsuported mouse interface"); return; } mouse = (MouseInterface *)interface; } else if (strcmp(interface->type, VD_INTERFACE_MIGRATION) == 0) { red_printf("VD_INTERFACE_MIGRATION"); if (mig) { red_printf("already have migration"); return; } if (interface->major_version != VD_INTERFACE_MIGRATION_MAJOR || interface->minor_version < VD_INTERFACE_MIGRATION_MINOR) { red_printf("unsuported migration interface"); return; } mig = (MigrationInterface *)interface; reds->mig_notifier = mig->register_notifiers(mig, MIGRATION_NOTIFY_SPICE_KEY, reds_mig_started, reds_mig_finished, reds_mig_recv, NULL); if (reds->mig_notifier == INVALID_VD_OBJECT_REF) { red_error("migration register failed"); } } else if (strcmp(interface->type, VD_INTERFACE_QXL) == 0) { QXLInterface *qxl_interface; red_printf("VD_INTERFACE_QXL"); if (interface->major_version != VD_INTERFACE_QXL_MAJOR || interface->minor_version < VD_INTERFACE_QXL_MINOR) { red_printf("unsuported qxl interface"); return; } qxl_interface = (QXLInterface *)interface; red_dispatcher_init(qxl_interface); } else if (strcmp(interface->type, VD_INTERFACE_QTERM) == 0) { static int was_here = FALSE; red_printf("VD_INTERFACE_QTERM"); if (was_here) { return; } was_here = TRUE; if (interface->major_version != VD_INTERFACE_QTERM_MAJOR || interface->minor_version < VD_INTERFACE_QTERM_MINOR) { red_printf("unsuported qterm interface"); return; } add_monitor_action_commands((QTermInterface *)interface); add_monitor_info_commands((QTermInterface *)interface); } else if (strcmp(interface->type, VD_INTERFACE_QTERM2) == 0) { static int was_here = FALSE; red_printf("VD_INTERFACE_QTERM2"); if (was_here) { return; } was_here = TRUE; if (interface->major_version != VD_INTERFACE_QTERM2_MAJOR || interface->minor_version < VD_INTERFACE_QTERM2_MINOR) { red_printf("unsuported qterm interface"); return; } add_monitor_action_commands_2((QTerm2Interface *)interface); add_monitor_info_commands_2((QTerm2Interface *)interface); } else if (strcmp(interface->type, VD_INTERFACE_TABLET) == 0) { red_printf("VD_INTERFACE_TABLET"); if (tablet) { red_printf("already have tablet"); return; } if (interface->major_version != VD_INTERFACE_TABLET_MAJOR || interface->minor_version < VD_INTERFACE_TABLET_MINOR) { red_printf("unsuported tablet interface"); return; } tablet = (TabletInterface *)interface; reds_update_mouse_mode(); if (reds->is_client_mouse_allowed) { tablet->set_logical_size(tablet, reds->monitor_mode.x_res, reds->monitor_mode.y_res); } } else if (strcmp(interface->type, VD_INTERFACE_PLAYBACK) == 0) { red_printf("VD_INTERFACE_PLAYBACK"); if (interface->major_version != VD_INTERFACE_PLAYBACK_MAJOR || interface->minor_version < VD_INTERFACE_PLAYBACK_MINOR) { red_printf("unsuported playback interface"); return; } snd_attach_playback((PlaybackInterface *)interface); } else if (strcmp(interface->type, VD_INTERFACE_RECORD) == 0) { red_printf("VD_INTERFACE_RECORD"); if (interface->major_version != VD_INTERFACE_RECORD_MAJOR || interface->minor_version < VD_INTERFACE_RECORD_MINOR) { red_printf("unsuported record interface"); return; } snd_attach_record((RecordInterface *)interface); } else if (strcmp(interface->type, VD_INTERFACE_VDI_PORT) == 0) { red_printf("VD_INTERFACE_VDI_PORT"); if (vdagent) { red_printf("vdi port already attached"); return; } if (interface->major_version != VD_INTERFACE_VDI_PORT_MAJOR || interface->minor_version < VD_INTERFACE_VDI_PORT_MINOR) { red_printf("unsuported vdi port interface"); return; } attach_to_red_agent((VDIPortInterface *)interface); } break; case VD_INTERFACE_REMOVING: if (strcmp(interface->type, VD_INTERFACE_TABLET) == 0) { red_printf("remove VD_INTERFACE_TABLET"); if (interface == (VDInterface *)tablet) { tablet = NULL; reds_update_mouse_mode(); } break; } else if (strcmp(interface->type, VD_INTERFACE_PLAYBACK) == 0) { red_printf("remove VD_INTERFACE_PLAYBACK"); snd_detach_playback((PlaybackInterface *)interface); break; } else if (strcmp(interface->type, VD_INTERFACE_RECORD) == 0) { red_printf("remove VD_INTERFACE_RECORD"); snd_detach_record((RecordInterface *)interface); break; } else if (strcmp(interface->type, VD_INTERFACE_VDI_PORT) == 0) { red_printf("remove VD_INTERFACE_VDI_PORT"); if (interface == (VDInterface *)vdagent) { reds_agent_remove(); } break; } red_error("VD_INTERFACE_REMOVING unsupported"); break; } } static void free_external_agent_buff(VDIPortBuf *in_buf) { VDIPortState *state = &reds->agent_state; ring_add(&state->external_bufs, &in_buf->link); add_token(); } static void free_internal_agent_buff(VDIPortBuf *in_buf) { VDIPortState *state = &reds->agent_state; ring_add(&state->internal_bufs, &in_buf->link); if (reds->inputs_state && reds->inputs_state->pending_mouse_event) { reds_handle_agent_mouse_event(); } } void reds_prepare_read_buf(RedsOutItem *in_nuf, struct iovec* vec, int *len) { VDIReadBuf *buf = (VDIReadBuf *)in_nuf; vec[0].iov_base = &buf->header; vec[0].iov_len = sizeof(buf->header); vec[1].iov_base = buf->data; vec[1].iov_len = buf->len; *len = 2; } void reds_release_read_buf(RedsOutItem *in_nuf) { VDIReadBuf *buf = (VDIReadBuf *)in_nuf; ring_add(&reds->agent_state.read_bufs, &buf->out_item.link); read_from_vdi_port(); } static void init_vd_agent_resources() { VDIPortState *state = &reds->agent_state; int i; ring_init(&state->external_bufs); ring_init(&state->internal_bufs); ring_init(&state->write_queue); ring_init(&state->read_bufs); state->read_state = VDI_PORT_READ_STATE_READ_HADER; state->recive_pos = (uint8_t *)&state->vdi_chunk_header; state->recive_len = sizeof(state->vdi_chunk_header); for (i = 0; i < REDS_AGENT_WINDOW_SIZE; i++) { VDAgentExtBuf *buf = (VDAgentExtBuf *)malloc(sizeof(VDAgentExtBuf)); if (!buf) { PANIC("alloc failed"); } memset(buf, 0, sizeof(*buf)); ring_item_init(&buf->base.link); buf->base.chunk_header.port = VDP_CLIENT_PORT; buf->base.free = free_external_agent_buff; ring_add(&reds->agent_state.external_bufs, &buf->base.link); } for (i = 0; i < REDS_NUM_INTERNAL_AGENT_MESSAGES; i++) { VDInternalBuf *buf = (VDInternalBuf *)malloc(sizeof(VDInternalBuf)); if (!buf) { PANIC("alloc failed"); } memset(buf, 0, sizeof(*buf)); ring_item_init(&buf->base.link); buf->base.free = free_internal_agent_buff; buf->base.chunk_header.port = VDP_SERVER_PORT; buf->base.chunk_header.size = sizeof(VDAgentMessage) + sizeof(VDAgentMouseState); buf->header.protocol = VD_AGENT_PROTOCOL; buf->header.type = VD_AGENT_MOUSE_STATE; buf->header.opaque = 0; buf->header.size = sizeof(VDAgentMouseState); ring_add(&reds->agent_state.internal_bufs, &buf->base.link); } for (i = 0; i < REDS_VDI_PORT_NUM_RECIVE_BUFFS; i++) { VDIReadBuf *buf = (VDIReadBuf *)malloc(sizeof(VDIReadBuf)); if (!buf) { PANIC("alloc failed"); } memset(buf, 0, sizeof(*buf)); buf->out_item.prepare = reds_prepare_read_buf; buf->out_item.release = reds_release_read_buf; buf->header.type = RED_AGENT_DATA; buf->header.sub_list = 0; ring_item_init(&buf->out_item.link); ring_add(&reds->agent_state.read_bufs, &buf->out_item.link); } state->plug.major_version = VD_INTERFACE_VDI_PORT_MAJOR; state->plug.minor_version = VD_INTERFACE_VDI_PORT_MINOR; state->plug.wakeup = reds_agent_wakeup; } static const char *version_string = VERSION; static const char *patch_string = PATCHID; static const char *distro_string = DISTRIBUTION; static void do_spice_init(CoreInterface *core_interface) { VDInterface *interface = NULL; red_printf("starting %s%s%s%s%s", version_string, strlen(patch_string) ? "-" : "", patch_string, strlen(distro_string) ? "." : "", distro_string); if (core_interface->base.base_version != VM_INTERFACE_VERSION) { red_error("bad base interface version"); } if (core_interface->base.major_version != VD_INTERFACE_CORE_MAJOR) { red_error("bad core interface version"); } core = core_interface; if (core_interface->base.minor_version > 1) { log_proc = core->log; } reds->listen_socket = -1; reds->secure_listen_socket = -1; reds->peer = NULL; reds->in_handler.handle_message = reds_main_handle_message; ring_init(&reds->outgoing.pipe); reds->outgoing.vec = reds->outgoing.vec_buf; init_vd_agent_resources(); if (!(reds->mig_timer = core->create_timer(core, migrate_timout, NULL))) { red_error("migration timer create failed"); } if (!(reds->key_modifiers_timer = core->create_timer(core, key_modifiers_sender, NULL))) { red_error("key modifiers timer create failed"); } if (core->next) { while ((interface = core->next(core, interface))) { interface_change_notifier(&reds, interface, VD_INTERFACE_ADDING); } } if (core->register_change_notifiers) { core->register_change_notifiers(core, &reds, interface_change_notifier); } #ifdef RED_STATISTICS int shm_name_len = strlen(REDS_STAT_SHM_NAME) + 20; int fd; if (!(reds->stat_shm_name = (char *)malloc(shm_name_len))) { red_error("stat_shm_name alloc failed"); } snprintf(reds->stat_shm_name, shm_name_len, REDS_STAT_SHM_NAME, getpid()); if ((fd = shm_open(reds->stat_shm_name, O_CREAT | O_RDWR, 0444)) == -1) { red_error("statistics shm_open failed, %s", strerror(errno)); } if (ftruncate(fd, REDS_STAT_SHM_SIZE) == -1) { red_error("statistics ftruncate failed, %s", strerror(errno)); } reds->stat = mmap(NULL, REDS_STAT_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (reds->stat == (RedsStat *)MAP_FAILED) { red_error("statistics mmap failed, %s", strerror(errno)); } memset(reds->stat, 0, REDS_STAT_SHM_SIZE); reds->stat->magic = REDS_STAT_MAGIC; reds->stat->version = REDS_STAT_VERSION; reds->stat->root_index = INVALID_STAT_REF; if (pthread_mutex_init(&reds->stat_lock, NULL)) { red_error("mutex init failed"); } if (!(reds->ping_timer = core->create_timer(core, ping_timer_cb, NULL))) { red_error("ping timer create failed"); } reds->ping_interval = PING_INTERVAL; #endif if (!(reds->mm_timer = core->create_timer(core, mm_timer_proc, NULL))) { red_error("mm timer create failed"); } core->arm_timer(core, reds->mm_timer, MM_TIMER_GRANULARITY_MS); reds_init_net(); if (reds->secure_listen_socket != -1) { reds_init_ssl(); } inputs_init(); reds->mouse_mode = RED_MOUSE_MODE_SERVER; atexit(reds_exit); } void __attribute__ ((visibility ("default"))) spice_init(CoreInterface *core_interface) { spice_server_new(); do_spice_init(core_interface); } /* new interface */ SpiceServer *spice_server_new(void) { /* we can't handle multiple instances (yet) */ ASSERT(reds == NULL); if (!(reds = malloc(sizeof(RedsState)))) { red_error("reds alloc failed"); } memset(reds, 0, sizeof(RedsState)); return reds; } int spice_server_init(SpiceServer *s, CoreInterface *core) { ASSERT(reds == s); do_spice_init(core); if (default_renderer) red_dispatcher_add_renderer(default_renderer); return 0; } void spice_server_destroy(SpiceServer *s) { ASSERT(reds == s); reds_exit(); } int spice_server_set_port(SpiceServer *s, int port) { ASSERT(reds == s); if (port < 0 || port > 0xffff) return -1; spice_port = port; return 0; } void spice_server_set_addr(SpiceServer *s, const char *addr, int flags) { ASSERT(reds == s); strncpy(spice_addr, addr, sizeof(spice_addr)); if (flags & SPICE_ADDR_FLAG_IPV4_ONLY) { spice_family = PF_INET; } if (flags & SPICE_ADDR_FLAG_IPV6_ONLY) { spice_family = PF_INET6; } } int spice_server_set_noauth(SpiceServer *s) { ASSERT(reds == s); memset(taTicket.password, 0, sizeof(taTicket.password)); ticketing_enabled = 0; return 0; } int spice_server_set_ticket(SpiceServer *s, const char *passwd, int lifetime, int fail_if_connected, int disconnect_if_connected) { ASSERT(reds == s); if (reds->peer) { if (fail_if_connected) return -1; if (disconnect_if_connected) reds_disconnect(); } on_activating_ticketing(); ticketing_enabled = 1; if (lifetime == 0) { taTicket.expiration_time = INT_MAX; } else { time_t now = time(NULL); taTicket.expiration_time = now + lifetime; } if (passwd != NULL) { strncpy(taTicket.password, passwd, sizeof(taTicket.password)); } else { memset(taTicket.password, 0, sizeof(taTicket.password)); taTicket.expiration_time = 0; } return 0; } int spice_server_set_tls(SpiceServer *s, int port, const char *ca_cert_file, const char *certs_file, const char *private_key_file, const char *key_passwd, const char *dh_key_file, const char *ciphersuite) { ASSERT(reds == s); if (port == 0 || ca_cert_file == NULL || certs_file == NULL || private_key_file == NULL) { return -1; } if (port < 0 || port > 0xffff) return -1; memset(&ssl_parameters, 0, sizeof(ssl_parameters)); spice_secure_port = port; strncpy(ssl_parameters.ca_certificate_file, ca_cert_file, sizeof(ssl_parameters.ca_certificate_file)-1); strncpy(ssl_parameters.certs_file, certs_file, sizeof(ssl_parameters.certs_file)-1); strncpy(ssl_parameters.private_key_file, private_key_file, sizeof(ssl_parameters.private_key_file)-1); if (key_passwd) { strncpy(ssl_parameters.keyfile_password, key_passwd, sizeof(ssl_parameters.keyfile_password)-1); } if (ciphersuite) { strncpy(ssl_parameters.ciphersuite, ciphersuite, sizeof(ssl_parameters.ciphersuite)-1); } if (dh_key_file) { strncpy(ssl_parameters.dh_key_file, dh_key_file, sizeof(ssl_parameters.dh_key_file)-1); } return 0; } int spice_server_set_image_compression(SpiceServer *s, spice_image_compression_t comp) { ASSERT(reds == s); set_image_compression(comp); return 0; } spice_image_compression_t spice_server_get_image_compression(SpiceServer *s) { ASSERT(reds == s); return image_compression; } int spice_server_set_channel_security(SpiceServer *s, spice_channel_t channel, int security) { ASSERT(reds == s); if (channel == SPICE_CHANNEL_ALL) { set_all_channels_security(security); } else { set_one_channel_security(channel, security); } return 0; } int spice_server_set_mouse_absolute(SpiceServer *s, int absolute) { uint32_t mode = absolute ? RED_MOUSE_MODE_CLIENT : RED_MOUSE_MODE_SERVER; ASSERT(reds == s); reds_set_mouse_mode(mode); return 0; } int spice_server_get_sock_info(SpiceServer *s, struct sockaddr *sa, socklen_t *salen) { ASSERT(reds == s); if (!reds->peer) return -1; if (getsockname(reds->peer->socket, sa, salen) < 0) return -1; return 0; } int spice_server_get_peer_info(SpiceServer *s, struct sockaddr *sa, socklen_t *salen) { ASSERT(reds == s); if (!reds->peer) return -1; if (getpeername(reds->peer->socket, sa, salen) < 0) return -1; return 0; } int spice_server_add_renderer(SpiceServer *s, const char *name) { ASSERT(reds == s); if (!red_dispatcher_add_renderer(name)) return -1; default_renderer = NULL; return 0; } int spice_server_add_interface(SpiceServer *s, VDInterface *interface) { ASSERT(reds == s); interface_change_notifier(NULL, interface, VD_INTERFACE_ADDING); return 0; } int spice_server_remove_interface(SpiceServer *s, VDInterface *interface) { ASSERT(reds == s); interface_change_notifier(NULL, interface, VD_INTERFACE_REMOVING); return 0; } int spice_server_kbd_leds(SpiceServer *s, KeyboardInterface *kbd, int leds) { ASSERT(reds == s); reds_on_keyborad_leads_change(NULL, leds); return 0; } static void reds_free_mig_switch_host_item(RedsOutItem *item) { if (!item) { return; } ASSERT((SimpleOutItem*)item == reds->mig_switch_host_item); free(item); reds->mig_switch_host_item = NULL; } int spice_server_migrate_info(SpiceServer *s, const char* dest, int port, int secure_port, const char* cert_subject) { int host_len; int subject_len; RedMigrationSwitchHost *mig_msg; ASSERT(reds == s); if (reds->mig_switch_host_item) { reds_free_mig_switch_host_item(&reds->mig_switch_host_item->base); } if ((port == -1 && secure_port == -1) || !dest) { red_printf("invalid args port %d secure-port %d host %s", port, secure_port, dest ? dest : "NULL"); return -1; } host_len = strlen(dest) + 1; subject_len = cert_subject ? strlen(cert_subject) + 1 : 0; reds->mig_switch_host_item = new_simple_out_item(RED_MIGRATE_SWITCH_HOST, sizeof(RedMigrationSwitchHost) + host_len + subject_len); if (!(reds->mig_switch_host_item)) { red_printf("alloc item failed"); return -1; } reds->mig_switch_host_item->base.release = reds_free_mig_switch_host_item; mig_msg = (RedMigrationSwitchHost*)reds->mig_switch_host_item->data; mig_msg->port = port; mig_msg->sport = secure_port; mig_msg->host_offset = sizeof(RedMigrationSwitchHost); mig_msg->host_size = host_len; mig_msg->cert_subject_offset = sizeof(RedMigrationSwitchHost) + host_len; mig_msg->cert_subject_size = subject_len; memcpy((uint8_t*)(mig_msg) + mig_msg->host_offset, dest, host_len); memcpy((uint8_t*)(mig_msg) + mig_msg->cert_subject_offset, cert_subject, subject_len); return 0; } int spice_server_migrate_start(SpiceServer *s) { ASSERT(reds == s); red_printf(""); if (reds->listen_socket != -1) { core->set_file_handlers(core, reds->listen_socket, NULL, NULL, NULL); } if (reds->secure_listen_socket != -1) { core->set_file_handlers(core, reds->secure_listen_socket, NULL, NULL, NULL); } if (reds->peer == NULL) { red_printf("not connected to peer"); return 0; } return 0; } static inline uint64_t get_now() { struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); return time.tv_sec * 1000000 + (time.tv_nsec / 1000); } int spice_server_migrate_end(SpiceServer *s, int completed) { ASSERT(reds == s); red_printf("status %s", completed ? "success" : "failure"); if (reds->listen_socket != -1) { core->set_file_handlers(core, reds->listen_socket, reds_accept, NULL, NULL); } if (reds->secure_listen_socket != -1) { core->set_file_handlers(core, reds->secure_listen_socket, reds_accept_ssl_connection, NULL, NULL); } if (reds->peer == NULL) { red_printf("no peer connected"); if (reds->mig_switch_host_item) { reds_free_mig_switch_host_item(&reds->mig_switch_host_item->base); } return 0; } if (completed) { uint64_t end_time; if ((RED_VERSION_MAJOR == 1) && (reds->peer_minor_version < 2)) { red_printf("minor version mismatch client %u server %u", reds->peer_minor_version, RED_VERSION_MINOR); reds_disconnect(); return 0; } if (!reds->mig_switch_host_item) { red_printf("missing pre-migrate information"); reds_disconnect(); return -1; } reds_push_pipe_item(&reds->mig_switch_host_item->base); end_time = get_now() + MIGRATE_TIMEOUT * 1000; // waiting for the client to receive the message and diconnect while (reds->peer) { usleep(10000); if (get_now() > end_time) { red_printf("timeout"); break; } reds_main_read(NULL); reds_push(); } if (!reds->peer) { red_printf("client disconnected"); } reds_disconnect(); return 0; } else { if (reds->mig_switch_host_item) { reds_free_mig_switch_host_item(&reds->mig_switch_host_item->base); } return 0; } }