summaryrefslogtreecommitdiffstats
path: root/server/reds.c
diff options
context:
space:
mode:
authorYaniv Kamay <ykamay@redhat.com>2009-09-19 21:25:46 +0300
committerYaniv Kamay <ykamay@redhat.com>2009-10-14 15:06:41 +0200
commitc1b79eb035fa158fb2ac3bc8e559809611070016 (patch)
tree3348dd749a700dedf87c9b16fe8be77c62928df8 /server/reds.c
downloadspice-c1b79eb035fa158fb2ac3bc8e559809611070016.tar.gz
spice-c1b79eb035fa158fb2ac3bc8e559809611070016.tar.xz
spice-c1b79eb035fa158fb2ac3bc8e559809611070016.zip
fresh start
Diffstat (limited to 'server/reds.c')
-rw-r--r--server/reds.c4996
1 files changed, 4996 insertions, 0 deletions
diff --git a/server/reds.c b/server/reds.c
new file mode 100644
index 00000000..067304d7
--- /dev/null
+++ b/server/reds.c
@@ -0,0 +1,4996 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <limits.h>
+#include <time.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/user.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#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 1
+#define REDS_MIG_CONTINUE 1
+#define REDS_MIG_ABORT 2
+
+#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 struct in_addr spice_addr = {INADDR_ANY};
+static int ticketing_enabled = 1; //Ticketing is enabled by default
+static pthread_mutex_t *lock_cs;
+static long *lock_count;
+uint32_t streaming_video = TRUE;
+image_compression_t image_compression = 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 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;
+} 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;
+
+#define CHANNEL_SECURITY_NON (1 << 0)
+#define CHANNEL_SECURITY_SSL (1 << 1)
+
+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;
+
+static uint8_t zero_page[PAGE_SIZE] = {0};
+
+static void reds_main_write(void *data);
+static void reds_push();
+
+static ChannelSecurityOptions *channels_security = NULL;
+static int default_channel_security = CHANNEL_SECURITY_NON | 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 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 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;
+ }
+ }
+}
+
+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;
+}
+
+typedef struct SimpleOutItem {
+ RedsOutItem base;
+ RedDataHeader header;
+ uint8_t data[0];
+} SimpleOutItem;
+
+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(PAGE_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 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(&ltime);
+ 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 & CHANNEL_SECURITY_SSL)) || (!link->peer->ssl &&
+ (security & 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;
+ }
+
+ 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(uint16_t port)
+{
+ struct sockaddr_in addr;
+ int sock;
+ int flags;
+
+ if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+ red_error("socket failed, %s", strerror(errno));
+ }
+
+ flags = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)) < 0) {
+ red_error("socket set sockopt failed, %s", strerror(errno));
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = spice_addr.s_addr;
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ red_error("bind failed, %s", strerror(errno));
+ }
+
+ if ((flags = fcntl(sock, F_GETFL)) == -1) {
+ red_error("fcntl get failed, %s", strerror(errno));
+ }
+
+ if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
+ red_error("fcntl set failed, %s", strerror(errno));
+ }
+
+ if (listen(sock, 2) == -1) {
+ red_error("listen failed, %s", strerror(errno));
+ }
+
+ return sock;
+}
+
+static void reds_init_net()
+{
+ if (spice_port != -1) {
+ reds->listen_socket = reds_init_socket(spice_port);
+ 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_secure_port);
+ 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()
+{
+ SSL_METHOD *ssl_method;
+ 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
+
+ load_dh_params(reds->ctx, ssl_parameters.dh_key_file);
+
+ SSL_CTX_set_session_id_context(reds->ctx, (const unsigned char *)"SPICE", 5);
+ 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(&ltime);
+ 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_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_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 IMAGE_COMPRESS_AUTO_GLZ:
+ core->term_printf(core, " ic=auto_glz");
+ break;
+ case IMAGE_COMPRESS_AUTO_LZ:
+ core->term_printf(core, " ic=auto_lz");
+ break;
+ case IMAGE_COMPRESS_QUIC:
+ core->term_printf(core, " ic=quic");
+ break;
+ case IMAGE_COMPRESS_LZ:
+ core->term_printf(core, " ic=lz");
+ break;
+ case IMAGE_COMPRESS_GLZ:
+ core->term_printf(core, " ic=glz");
+ break;
+ case IMAGE_COMPRESS_OFF:
+ core->term_printf(core, " ic=off");
+ break;
+ case IMAGE_COMPRESS_INVALID:
+ default:
+ core->term_printf(core, " ic=invalid");
+ }
+
+ core->term_printf(core, " sv=%s", streaming_video ? "on" : "off");
+ core->term_printf(core, " playback-compression=%s\n",
+ snd_get_playback_compression() ? "on" : "off");
+}
+
+static void set_image_compression(image_compression_t val)
+{
+ if (val == image_compression) {
+ return;
+ }
+ image_compression = val;
+ red_dispatcher_on_ic_change();
+}
+
+static image_compression_t reds_get_image_compression(const char *val)
+{
+ if ((strcmp(val, "on") == 0) || (strcmp(val, "auto_glz") == 0)) {
+ return IMAGE_COMPRESS_AUTO_GLZ;
+ } else if (strcmp(val, "auto_lz") == 0) {
+ return IMAGE_COMPRESS_AUTO_LZ;
+ } else if (strcmp(val, "quic") == 0) {
+ return IMAGE_COMPRESS_QUIC;
+ } else if (strcmp(val, "glz") == 0) {
+ return IMAGE_COMPRESS_GLZ;
+ } else if (strcmp(val, "lz") == 0) {
+ return IMAGE_COMPRESS_LZ;
+ } else if (strcmp(val, "off") == 0) {
+ return IMAGE_COMPRESS_OFF;
+ }
+ return IMAGE_COMPRESS_INVALID;
+}
+
+static void reds_do_set_image_compression(const char *val)
+{
+ image_compression_t real_val = reds_get_image_compression(val);
+ if (real_val == IMAGE_COMPRESS_INVALID) {
+ core->term_printf(core, "bad image compression arg\n");
+ return;
+ }
+ set_image_compression(real_val);
+}
+
+static void reds_do_set_streaming_video(const char *val)
+{
+ uint32_t 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 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_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_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 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},
+};
+
+enum {
+ CHANNEL_NAME_INVALID,
+ CHANNEL_NAME_ALL,
+ CHANNEL_NAME_MAIN,
+ CHANNEL_NAME_DISPLAY,
+ CHANNEL_NAME_INPUTS,
+ CHANNEL_NAME_CURSOR,
+ CHANNEL_NAME_PLAYBACK,
+ CHANNEL_NAME_RECORD,
+};
+
+static OptionsMap _channel_map[] = {
+ {"all", CHANNEL_NAME_ALL},
+ {"main", CHANNEL_NAME_MAIN},
+ {"display", CHANNEL_NAME_DISPLAY},
+ {"inputs", CHANNEL_NAME_INPUTS},
+ {"cursor", CHANNEL_NAME_CURSOR},
+ {"playback", CHANNEL_NAME_PLAYBACK},
+ {"record", CHANNEL_NAME_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 CHANNEL_NAME_ALL:
+ all++;
+ break;
+ case CHANNEL_NAME_MAIN:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_MAIN, security);
+ break;
+ case CHANNEL_NAME_DISPLAY:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_DISPLAY, security);
+ break;
+ case CHANNEL_NAME_INPUTS:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_INPUTS, security);
+ break;
+ case CHANNEL_NAME_CURSOR:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_CURSOR, security);
+ break;
+ case CHANNEL_NAME_PLAYBACK:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_PLAYBACK, security);
+ break;
+ case CHANNEL_NAME_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: {
+ struct hostent* host_addr;
+ if (!val) {
+ goto error;
+ }
+ if ((host_addr = gethostbyname(val)) == NULL || host_addr->h_addrtype != AF_INET) {
+ goto error;
+ }
+ ASSERT(host_addr->h_length == sizeof(spice_addr));
+ memcpy(&spice_addr, host_addr->h_addr, sizeof(spice_addr));
+ break;
+ }
+ case SPICE_OPTION_IMAGE_COMPRESSION:
+ if (!val) {
+ goto error;
+ }
+ image_compression = reds_get_image_compression(val);
+ if (image_compression == 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, CHANNEL_SECURITY_SSL)) {
+ goto error;
+ }
+ break;
+ case SPICE_UNSECURED_CHANNELS:
+ if (!val || !set_channels_security(val, CHANNEL_SECURITY_NON)) {
+ goto error;
+ }
+ break;
+ case SPICE_OPTION_STREAMING_VIDEO:
+ if (!val) {
+ goto error;
+ }
+ if (strcmp(val, "off") == 0) {
+ streaming_video = FALSE;
+ } else if (strcmp(val, "on") != 0) {
+ 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=<port>][,sport=<port>][,host=<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 <host> port <port> and/or sport <port>",
+ " 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;
+} RedsMigSpice;
+
+typedef struct RedsMigSpiceMessage {
+ uint32_t link_id;
+} RedsMigSpiceMessage;
+
+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;
+ if (!(item = new_simple_out_item(RED_MIGRATE_BEGIN, sizeof(RedMigrationBegin) + host_len))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ migrate = (RedMigrationBegin *)item->data;
+ migrate->port = s->port;
+ migrate->sport = s->sport;
+ memcpy(migrate->host, s->host, host_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_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 = RED_TICKET_PUBKEY_BYTES;
+ spice_migration->read.end_pos = 0;
+ spice_migration->read.handle_data = reds_mig_send_ticket;
+
+ 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;
+ 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;
+ }
+
+ 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_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");
+
+ 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;
+ }
+
+ resault = REDS_MIG_CONTINUE;
+ reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
+
+ 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,
+ "",
+ "<on|off>");
+ mon->add_action_command_handler(mon, "spice", "set_playback_compression", "s",
+ reds_do_set_playback_compression,
+ "",
+ "<on|off>");
+ mon->add_action_command_handler(mon, "spice", "set_ticket", "ss?",
+ reds_do_set_ticket,
+ "<password> [expiration=<seconds>]"
+ "[,connected=keep|disconnect|fail]",
+ "set the spice connection ticket");
+ mon->add_action_command_handler(mon, "spice", "set_ticket64", "ss?",
+ reds_do_set_ticket64,
+ "<password> [expiration=<seconds>]"
+ " [,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,
+ "",
+ "<on|off>");
+#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_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 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(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_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;
+}
+
+const char *version_string = VERSION;
+
+void __attribute__ ((visibility ("default"))) spice_init(CoreInterface *core_interface)
+{
+ VDInterface *interface = NULL;
+
+ red_printf("starting %s", version_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;
+ }
+ if (!(reds = malloc(sizeof(RedsState)))) {
+ red_error("reds alloc failed");
+ }
+ memset(reds, 0, sizeof(RedsState));
+ 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");
+ }
+
+ while ((interface = core->next(core, interface))) {
+ interface_change_notifier(&reds, interface, VD_INTERFACE_ADDING);
+ }
+ 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);
+}
+