mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 22:48:19 +00:00
5609 lines
166 KiB
C
5609 lines
166 KiB
C
/*
|
|
Copyright (C) 2009 Red Hat, Inc.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, see <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 <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 <spice/protocol.h>
|
|
#include <spice/vd_agent.h>
|
|
|
|
#include "red_common.h"
|
|
#include "red_dispatcher.h"
|
|
#include "snd_worker.h"
|
|
#include <spice/stats.h>
|
|
#include "stat.h"
|
|
#include "ring.h"
|
|
#include "config.h"
|
|
#ifdef HAVE_SLIRP
|
|
#include "red_tunnel_worker.h"
|
|
#endif
|
|
|
|
CoreInterface *core = NULL;
|
|
static MigrationInterface *mig = NULL;
|
|
static KeyboardInterface *keyboard = NULL;
|
|
static MouseInterface *mouse = NULL;
|
|
static TabletInterface *tablet = NULL;
|
|
static VDIPortInterface *vdagent = NULL;
|
|
|
|
#define MIGRATION_NOTIFY_SPICE_KEY "spice_mig_ext"
|
|
|
|
#define REDS_MIG_VERSION 3
|
|
#define REDS_MIG_CONTINUE 1
|
|
#define REDS_MIG_ABORT 2
|
|
#define REDS_MIG_DIFF_VERSION 3
|
|
|
|
#define REDS_AGENT_WINDOW_SIZE 10
|
|
#define REDS_TOKENS_TO_SEND 5
|
|
#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1
|
|
#define REDS_VDI_PORT_NUM_RECIVE_BUFFS 5
|
|
#define REDS_MAX_SEND_IOVEC 100
|
|
|
|
#define NET_TEST_WARMUP_BYTES 0
|
|
#define NET_TEST_BYTES (1024 * 250)
|
|
|
|
static int spice_port = -1;
|
|
static int spice_secure_port = -1;
|
|
static char spice_addr[256];
|
|
static int spice_family = PF_UNSPEC;
|
|
static char *default_renderer = "cairo";
|
|
|
|
static int ticketing_enabled = 1; //Ticketing is enabled by default
|
|
static pthread_mutex_t *lock_cs;
|
|
static long *lock_count;
|
|
uint32_t streaming_video = STREAM_VIDEO_FILTER;
|
|
spice_image_compression_t image_compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;
|
|
void *red_tunnel = NULL;
|
|
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) * SPICE_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, SpiceDataHeader *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[SPICE_MAX_PASSWORD_LENGTH];
|
|
time_t expiration_time;
|
|
} TicketAuthentication;
|
|
|
|
static TicketAuthentication taTicket;
|
|
|
|
typedef struct TicketInfo {
|
|
RSA *rsa;
|
|
int rsa_size;
|
|
BIGNUM *bn;
|
|
SpiceLinkEncryptedTicket 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;
|
|
SpiceDataHeader header;
|
|
uint8_t data[SPICE_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(SpiceStat) + REDS_MAX_STAT_NODES * sizeof(SpiceStatNode))
|
|
|
|
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;
|
|
SpiceStat *stat;
|
|
pthread_mutex_t stat_lock;
|
|
RedsStatValue roundtrip_stat;
|
|
VDObjectRef ping_timer;
|
|
int ping_interval;
|
|
#endif
|
|
uint32_t ping_id;
|
|
uint32_t net_test_id;
|
|
int net_test_stage;
|
|
int peer_minor_version;
|
|
} 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;
|
|
SpiceLinkHeader link_header;
|
|
SpiceLinkMess *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[SPICE_AGENT_MAX_DATA_SIZE];
|
|
VDIChunkHeader migrate_overflow;
|
|
} VDAgentExtBuf;
|
|
|
|
typedef struct __attribute__ ((__packed__)) VDInternalBuf {
|
|
VDIPortBuf base;
|
|
VDAgentMessage header;
|
|
union {
|
|
VDAgentMouseState mouse_state;
|
|
}
|
|
u;
|
|
VDIChunkHeader migrate_overflow;
|
|
} VDInternalBuf;
|
|
|
|
typedef struct RedSSLParameters {
|
|
char keyfile_password[256];
|
|
char certs_file[256];
|
|
char private_key_file[256];
|
|
char ca_certificate_file[256];
|
|
char dh_key_file[256];
|
|
char ciphersuite[256];
|
|
} RedSSLParameters;
|
|
|
|
typedef struct ChannelSecurityOptions ChannelSecurityOptions;
|
|
struct ChannelSecurityOptions {
|
|
uint32_t channel_id;
|
|
uint32_t options;
|
|
ChannelSecurityOptions *next;
|
|
};
|
|
|
|
typedef struct PingItem {
|
|
RedsOutItem base;
|
|
SpiceDataHeader header;
|
|
SpiceMsgPing ping;
|
|
int size;
|
|
} PingItem;
|
|
|
|
|
|
#define ZERO_BUF_SIZE 4096
|
|
|
|
static uint8_t zero_page[ZERO_BUF_SIZE] = {0};
|
|
|
|
static void reds_main_write(void *data);
|
|
static void reds_push();
|
|
|
|
static ChannelSecurityOptions *channels_security = NULL;
|
|
static int default_channel_security =
|
|
SPICE_CHANNEL_SECURITY_NONE | SPICE_CHANNEL_SECURITY_SSL;
|
|
|
|
static RedSSLParameters ssl_parameters;
|
|
|
|
|
|
void (*log_proc)(CoreInterface *core, LogLevel level, const char* component,
|
|
const char* format, ...) = NULL;
|
|
|
|
#define LOG_MESSAGE(level, format, ...) { \
|
|
if (log_proc) { \
|
|
log_proc(core, level, "spice", format, ## __VA_ARGS__ ); \
|
|
} \
|
|
}
|
|
|
|
static int args_is_empty(const VDICmdArg* args)
|
|
{
|
|
return !args || args[0].descriptor.type == ARG_TYPE_INVALID;
|
|
}
|
|
|
|
const int args_is_string(const VDICmdArg* args)
|
|
{
|
|
return !args_is_empty(args) && args->descriptor.type == ARG_TYPE_STRING;
|
|
}
|
|
|
|
const int args_is_int(const VDICmdArg* args)
|
|
{
|
|
return !args_is_empty(args) && args->descriptor.type == ARG_TYPE_INT;
|
|
}
|
|
|
|
static ChannelSecurityOptions *find_channel_security(int id)
|
|
{
|
|
ChannelSecurityOptions *now = channels_security;
|
|
while (now && now->channel_id != id) {
|
|
now = now->next;
|
|
}
|
|
return now;
|
|
}
|
|
|
|
static int reds_write(void *ctx, void *buf, size_t size)
|
|
{
|
|
int return_code;
|
|
int sock = (long)ctx;
|
|
size_t count = size;
|
|
|
|
return_code = write(sock, buf, count);
|
|
|
|
return (return_code);
|
|
}
|
|
|
|
static int reds_read(void *ctx, void *buf, size_t size)
|
|
{
|
|
int return_code;
|
|
int sock = (long)ctx;
|
|
size_t count = size;
|
|
|
|
return_code = read(sock, buf, count);
|
|
|
|
return (return_code);
|
|
}
|
|
|
|
static int reds_free(RedsStreamContext *peer)
|
|
{
|
|
close(peer->socket);
|
|
free(peer);
|
|
return 0;
|
|
}
|
|
|
|
static int reds_ssl_write(void *ctx, void *buf, size_t size)
|
|
{
|
|
int return_code;
|
|
int ssl_error;
|
|
SSL *ssl = ctx;
|
|
|
|
return_code = SSL_write(ssl, buf, size);
|
|
|
|
if (return_code < 0) {
|
|
ssl_error = SSL_get_error(ssl, return_code);
|
|
}
|
|
|
|
return (return_code);
|
|
}
|
|
|
|
static int reds_ssl_read(void *ctx, void *buf, size_t size)
|
|
{
|
|
int return_code;
|
|
int ssl_error;
|
|
SSL *ssl = ctx;
|
|
|
|
return_code = SSL_read(ssl, buf, size);
|
|
|
|
if (return_code < 0) {
|
|
ssl_error = SSL_get_error(ssl, return_code);
|
|
}
|
|
|
|
return (return_code);
|
|
}
|
|
|
|
static int reds_ssl_writev(void *ctx, const struct iovec *vector, int count)
|
|
{
|
|
int i;
|
|
int n;
|
|
int return_code = 0;
|
|
int ssl_error;
|
|
SSL *ssl = ctx;
|
|
|
|
for (i = 0; i < count; ++i) {
|
|
n = SSL_write(ssl, vector[i].iov_base, vector[i].iov_len);
|
|
if (n <= 0) {
|
|
ssl_error = SSL_get_error(ssl, n);
|
|
if (return_code <= 0) {
|
|
return n;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
return_code += n;
|
|
}
|
|
}
|
|
|
|
return return_code;
|
|
}
|
|
|
|
static int reds_ssl_free(RedsStreamContext *peer)
|
|
{
|
|
SSL_free(peer->ssl);
|
|
close(peer->socket);
|
|
free(peer);
|
|
return 0;
|
|
}
|
|
|
|
static void __reds_release_link(RedLinkInfo *link)
|
|
{
|
|
ASSERT(link->peer);
|
|
core->set_file_handlers(core, link->peer->socket, NULL, NULL, NULL);
|
|
free(link->link_mess);
|
|
BN_free(link->tiTicketing.bn);
|
|
if (link->tiTicketing.rsa) {
|
|
RSA_free(link->tiTicketing.rsa);
|
|
}
|
|
free(link);
|
|
}
|
|
|
|
static inline void reds_release_link(RedLinkInfo *link)
|
|
{
|
|
RedsStreamContext *peer = link->peer;
|
|
__reds_release_link(link);
|
|
peer->cb_free(peer);
|
|
}
|
|
|
|
static void reds_do_disable_ticketing(void)
|
|
{
|
|
ticketing_enabled = 0;
|
|
memset(taTicket.password, 0, sizeof(taTicket.password));
|
|
core->term_printf(core, "Ticketing is now disabled.\n");
|
|
}
|
|
|
|
static void reds_do_disable_ticketing_2(const VDICmdArg* args)
|
|
{
|
|
if (!args_is_empty(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
reds_do_disable_ticketing();
|
|
}
|
|
|
|
static char *base64decode(const char *input, int length)
|
|
{
|
|
BIO *b64;
|
|
BIO *bmem;
|
|
int n;
|
|
char *buffer = (char *)spice_malloc0(length);
|
|
char *inbuffer = (char *)spice_malloc0(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)
|
|
{
|
|
SpiceStatNode *node = &reds->stat->nodes[node_index];
|
|
|
|
if ((node->flags & SPICE_STAT_NODE_MASK_SHOW) == SPICE_STAT_NODE_MASK_SHOW) {
|
|
core->term_printf(core, "%*s%s", depth * STAT_TAB_LEN, "", node->name);
|
|
if (node->flags & SPICE_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()
|
|
{
|
|
SpiceStatNode *node;
|
|
int i;
|
|
|
|
for (i = 0; i <= REDS_MAX_STAT_NODES; i++) {
|
|
node = &reds->stat->nodes[i];
|
|
if (node->flags & SPICE_STAT_NODE_FLAG_VALUE) {
|
|
node->value = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_reset_statistics_2(const VDICmdArg* args)
|
|
{
|
|
if (!args_is_empty(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
do_reset_statistics();
|
|
}
|
|
|
|
void insert_stat_node(StatNodeRef parent, StatNodeRef ref)
|
|
{
|
|
SpiceStatNode *node = &reds->stat->nodes[ref];
|
|
uint32_t pos = INVALID_STAT_REF;
|
|
uint32_t node_index;
|
|
uint32_t *head;
|
|
SpiceStatNode *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;
|
|
SpiceStatNode *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 & SPICE_STAT_NODE_FLAG_ENABLED)) {
|
|
break;
|
|
}
|
|
}
|
|
ASSERT(!(node->flags & SPICE_STAT_NODE_FLAG_ENABLED));
|
|
node->value = 0;
|
|
node->flags = SPICE_STAT_NODE_FLAG_ENABLED | (visible ? SPICE_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(SpiceStatNode *node)
|
|
{
|
|
pthread_mutex_lock(&reds->stat_lock);
|
|
node->flags &= ~SPICE_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);
|
|
SpiceStatNode *node;
|
|
|
|
if (ref == INVALID_STAT_REF) {
|
|
return NULL;
|
|
}
|
|
node = &reds->stat->nodes[ref];
|
|
node->flags |= SPICE_STAT_NODE_FLAG_VALUE;
|
|
return &node->value;
|
|
}
|
|
|
|
void stat_remove_counter(uint64_t *counter)
|
|
{
|
|
stat_remove((SpiceStatNode *)(counter - offsetof(SpiceStatNode, 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;
|
|
SpiceDataHeader *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(SpiceDataHeader) <= end &&
|
|
buf + sizeof(SpiceDataHeader) + (header = (SpiceDataHeader *)buf)->size <= end) {
|
|
buf += sizeof(SpiceDataHeader) + 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;
|
|
SpiceDataHeader 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;
|
|
|
|
item = (SimpleOutItem *)spice_malloc(sizeof(*item) + message_size);
|
|
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()
|
|
{
|
|
SpiceMsgChannels* channels_info;
|
|
SimpleOutItem *item;
|
|
int message_size;
|
|
Channel *channel;
|
|
int i;
|
|
|
|
message_size = sizeof(SpiceMsgChannels) + reds->num_of_channels * sizeof(SpiceChannelId);
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_CHANNELS_LIST, message_size);
|
|
channels_info = (SpiceMsgChannels *)item->data;
|
|
channels_info->num_of_channels = reds->num_of_channels;
|
|
channel = reds->channels;
|
|
|
|
for (i = 0; i < reds->num_of_channels; i++) {
|
|
ASSERT(channel);
|
|
channels_info->channels[i].type = channel->type;
|
|
channels_info->channels[i].id = channel->id;
|
|
channel = channel->next;
|
|
}
|
|
reds_push_pipe_item(&item->base);
|
|
}
|
|
|
|
static void reds_prepare_ping_item(RedsOutItem *in_item, struct iovec* vec, int *len)
|
|
{
|
|
PingItem *item = (PingItem *)in_item;
|
|
|
|
vec[0].iov_base = &item->header;
|
|
vec[0].iov_len = sizeof(item->header);
|
|
vec[1].iov_base = &item->ping;
|
|
vec[1].iov_len = sizeof(item->ping);
|
|
int size = item->size;
|
|
int pos = 2;
|
|
while (size) {
|
|
ASSERT(pos < REDS_MAX_SEND_IOVEC);
|
|
int now = MIN(ZERO_BUF_SIZE, size);
|
|
size -= now;
|
|
vec[pos].iov_base = zero_page;
|
|
vec[pos].iov_len = now;
|
|
pos++;
|
|
}
|
|
*len = pos;
|
|
}
|
|
|
|
static void reds_free_ping_item(RedsOutItem *item)
|
|
{
|
|
free(item);
|
|
}
|
|
|
|
static int send_ping(int size)
|
|
{
|
|
struct timespec time_space;
|
|
PingItem *item;
|
|
|
|
if (!reds->peer) {
|
|
return FALSE;
|
|
}
|
|
item = spice_new(PingItem, 1);
|
|
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 = SPICE_MSG_PING;
|
|
item->header.size = sizeof(item->ping) + size;
|
|
item->header.sub_list = 0;
|
|
|
|
item->ping.id = ++reds->ping_id;
|
|
clock_gettime(CLOCK_MONOTONIC, &time_space);
|
|
item->ping.timestamp = time_space.tv_sec * 1000000LL + time_space.tv_nsec / 1000LL;
|
|
|
|
item->size = size;
|
|
reds_push_pipe_item(&item->base);
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef RED_STATISTICS
|
|
|
|
static void do_ping_client(const char *opt, int has_interval, int interval)
|
|
{
|
|
if (!reds->peer) {
|
|
red_printf("not connected to peer");
|
|
return;
|
|
}
|
|
|
|
if (!opt) {
|
|
send_ping(0);
|
|
} else if (!strcmp(opt, "on")) {
|
|
if (has_interval && interval > 0) {
|
|
reds->ping_interval = interval * 1000;
|
|
}
|
|
core->arm_timer(core, reds->ping_timer, reds->ping_interval);
|
|
core->term_printf(core, "ping on, interval %u s\n", reds->ping_interval / 1000);
|
|
} else if (!strcmp(opt, "off")) {
|
|
core->disarm_timer(core, reds->ping_timer);
|
|
core->term_printf(core, "ping off\n");
|
|
} else {
|
|
core->term_printf(core, "ping invalid option: %s\n", opt);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void do_ping_client_2(const VDICmdArg* args)
|
|
{
|
|
if (args_is_empty(args)) {
|
|
do_ping_client(NULL, FALSE, 0);
|
|
return;
|
|
}
|
|
|
|
if (!args_is_string(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
if (args_is_empty(&args[1])) {
|
|
do_ping_client(args[0].string_val, FALSE, 0);
|
|
return;
|
|
}
|
|
|
|
if (!args_is_int(&args[1])) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
do_ping_client(args[0].string_val, TRUE, args[1].int_val);
|
|
}
|
|
|
|
static void ping_timer_cb()
|
|
{
|
|
if (!reds->peer) {
|
|
red_printf("not connected to peer, ping off");
|
|
core->disarm_timer(core, reds->ping_timer);
|
|
return;
|
|
}
|
|
do_ping_client(NULL, 0, 0);
|
|
core->arm_timer(core, reds->ping_timer, reds->ping_interval);
|
|
}
|
|
|
|
static void do_info_rtt_client()
|
|
{
|
|
core->term_printf(core, "rtt=%uus, min/max/avg=%u/%u/%uus\n", reds->roundtrip_stat.value,
|
|
reds->roundtrip_stat.min, reds->roundtrip_stat.max,
|
|
reds->roundtrip_stat.average);
|
|
}
|
|
|
|
#endif
|
|
|
|
static void reds_send_mouse_mode()
|
|
{
|
|
SpiceMsgMainMouseMode *mouse_mode;
|
|
SimpleOutItem *item;
|
|
|
|
if (!reds->peer) {
|
|
return;
|
|
}
|
|
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_MOUSE_MODE, sizeof(SpiceMsgMainMouseMode));
|
|
mouse_mode = (SpiceMsgMainMouseMode *)item->data;
|
|
mouse_mode->supported_modes = SPICE_MOUSE_MODE_SERVER;
|
|
if (reds->is_client_mouse_allowed) {
|
|
mouse_mode->supported_modes |= SPICE_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 == SPICE_MOUSE_MODE_CLIENT && !allowed) {
|
|
reds_set_mouse_mode(SPICE_MOUSE_MODE_SERVER);
|
|
return;
|
|
}
|
|
reds_send_mouse_mode();
|
|
}
|
|
|
|
static void reds_send_agent_connected()
|
|
{
|
|
SimpleOutItem *item;
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_AGENT_CONNECTED, 0);
|
|
reds_push_pipe_item(&item->base);
|
|
}
|
|
|
|
static void reds_send_agent_disconnected()
|
|
{
|
|
SpiceMsgMainAgentDisconnect *disconnect;
|
|
SimpleOutItem *item;
|
|
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_AGENT_DISCONNECTED, sizeof(SpiceMsgMainAgentDisconnect));
|
|
disconnect = (SpiceMsgMainAgentDisconnect *)item->data;
|
|
disconnect->error_code = SPICE_LINK_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()
|
|
{
|
|
SpiceMsgMainAgentTokens *tokens;
|
|
SimpleOutItem *item;
|
|
|
|
if (!reds->peer) {
|
|
return;
|
|
}
|
|
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_AGENT_TOKEN, sizeof(SpiceMsgMainAgentTokens));
|
|
tokens = (SpiceMsgMainAgentTokens *)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;
|
|
SpiceDataHeader 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 = SPICE_MSG_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;
|
|
|
|
item = spice_new0(SendMainMigrateItem, 1);
|
|
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 > SPICE_AGENT_MAX_DATA_SIZE || n > SPICE_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) - SPICE_OFFSETOF(VDInternalBuf, header)) {
|
|
red_printf("bad buffer len");
|
|
reds_disconnect();
|
|
return FALSE;
|
|
}
|
|
buf = spice_new(VDInternalBuf, 1);
|
|
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) - SPICE_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, SpiceDataHeader *message)
|
|
{
|
|
switch (message->type) {
|
|
case SPICE_MSGC_MAIN_AGENT_START: {
|
|
SpiceMsgcMainAgentTokens *agent_start;
|
|
|
|
red_printf("agent start");
|
|
if (!reds->peer) {
|
|
return;
|
|
}
|
|
agent_start = (SpiceMsgcMainAgentTokens *)(message + 1);
|
|
reds->agent_state.client_agent_started = TRUE;
|
|
reds->agent_state.send_tokens = agent_start->num_tokens;
|
|
read_from_vdi_port();
|
|
break;
|
|
}
|
|
case SPICE_MSGC_MAIN_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("SPICE_MSGC_MAIN_AGENT_DATA race");
|
|
add_token();
|
|
break;
|
|
}
|
|
|
|
if (message->size > SPICE_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 SPICE_MSGC_MAIN_AGENT_TOKEN: {
|
|
SpiceMsgcMainAgentTokens *token;
|
|
|
|
if (!reds->agent_state.client_agent_started) {
|
|
red_printf("SPICE_MSGC_MAIN_AGENT_TOKEN race");
|
|
break;
|
|
}
|
|
|
|
token = (SpiceMsgcMainAgentTokens *)(message + 1);
|
|
reds->agent_state.send_tokens += token->num_tokens;
|
|
read_from_vdi_port();
|
|
break;
|
|
}
|
|
case SPICE_MSGC_MAIN_ATTACH_CHANNELS:
|
|
reds_send_channels();
|
|
break;
|
|
case SPICE_MSGC_MAIN_MIGRATE_CONNECTED:
|
|
red_printf("connected");
|
|
if (reds->mig_wait_connect) {
|
|
reds_mig_cleanup();
|
|
}
|
|
break;
|
|
case SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR:
|
|
red_printf("mig connect error");
|
|
if (reds->mig_wait_connect) {
|
|
reds_mig_cleanup();
|
|
}
|
|
break;
|
|
case SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST: {
|
|
switch (((SpiceMsgcMainMouseModeRequest *)(message + 1))->mode) {
|
|
case SPICE_MOUSE_MODE_CLIENT:
|
|
if (reds->is_client_mouse_allowed) {
|
|
reds_set_mouse_mode(SPICE_MOUSE_MODE_CLIENT);
|
|
} else {
|
|
red_printf("client mouse is disabled");
|
|
}
|
|
break;
|
|
case SPICE_MOUSE_MODE_SERVER:
|
|
reds_set_mouse_mode(SPICE_MOUSE_MODE_SERVER);
|
|
break;
|
|
default:
|
|
red_printf("unsupported mouse mode");
|
|
}
|
|
break;
|
|
}
|
|
case SPICE_MSGC_PONG: {
|
|
SpiceMsgPing *ping = (SpiceMsgPing *)(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 SPICE_MSGC_MIGRATE_FLUSH_MARK:
|
|
main_channel_push_migrate_data_item();
|
|
break;
|
|
case SPICE_MSGC_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 SPICE_MSGC_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)
|
|
{
|
|
SpiceLinkHeader header;
|
|
SpiceLinkReply ack;
|
|
Channel *channel;
|
|
BUF_MEM *bmBuf;
|
|
BIO *bio;
|
|
int ret;
|
|
|
|
header.magic = SPICE_MAGIC;
|
|
header.size = sizeof(ack);
|
|
header.major_version = SPICE_VERSION_MAJOR;
|
|
header.minor_version = SPICE_VERSION_MINOR;
|
|
|
|
ack.error = SPICE_LINK_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(SpiceLinkReply);
|
|
|
|
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, SPICE_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)
|
|
{
|
|
SpiceLinkHeader header;
|
|
SpiceLinkReply reply;
|
|
|
|
header.magic = SPICE_MAGIC;
|
|
header.size = sizeof(reply);
|
|
header.major_version = SPICE_VERSION_MAJOR;
|
|
header.minor_version = SPICE_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, SPICE_LINK_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, SPICE_LINK_ERR_BAD_CONNECTION_ID);
|
|
reds_release_link(link);
|
|
return;
|
|
}
|
|
reds_send_link_result(link, SPICE_LINK_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;
|
|
SpiceMsgMainInit *init;
|
|
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_INIT, sizeof(SpiceMsgMainInit));
|
|
init = (SpiceMsgMainInit *)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 = SPICE_MOUSE_MODE_SERVER;
|
|
if (reds->is_client_mouse_allowed) {
|
|
init->supported_mouse_modes |= SPICE_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 & SPICE_MOUSE_BUTTON_MASK_LEFT) | \
|
|
((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) << 1) | \
|
|
((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) >> 1))
|
|
|
|
#define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) \
|
|
(((state & SPICE_MOUSE_BUTTON_MASK_LEFT) ? VD_AGENT_LBUTTON_MASK : 0) | \
|
|
((state & SPICE_MOUSE_BUTTON_MASK_MIDDLE) ? VD_AGENT_MBUTTON_MASK : 0) | \
|
|
((state & SPICE_MOUSE_BUTTON_MASK_RIGHT) ? 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, SpiceDataHeader *header)
|
|
{
|
|
InputsState *state = (InputsState *)opaque;
|
|
uint8_t *buf = (uint8_t *)(header + 1);
|
|
|
|
switch (header->type) {
|
|
case SPICE_MSGC_INPUTS_KEY_DOWN: {
|
|
SpiceMsgcKeyDown *key_up = (SpiceMsgcKeyDown *)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 SPICE_MSGC_INPUTS_KEY_UP: {
|
|
SpiceMsgcKeyDown *key_down = (SpiceMsgcKeyDown *)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 SPICE_MSGC_INPUTS_MOUSE_MOTION: {
|
|
SpiceMsgcMouseMotion *mouse_motion = (SpiceMsgcMouseMotion *)buf;
|
|
|
|
if (++state->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0) {
|
|
SpiceDataHeader header;
|
|
|
|
header.serial = ++state->serial;
|
|
header.type = SPICE_MSG_INPUTS_MOUSE_MOTION_ACK;
|
|
header.size = 0;
|
|
header.sub_list = 0;
|
|
if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(SpiceDataHeader))
|
|
!= OUTGOING_OK) {
|
|
red_printf("motion ack failed");
|
|
reds_disconnect();
|
|
}
|
|
}
|
|
if (mouse && reds->mouse_mode == SPICE_MOUSE_MODE_SERVER) {
|
|
mouse->moution(mouse, mouse_motion->dx, mouse_motion->dy, 0,
|
|
RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state));
|
|
}
|
|
break;
|
|
}
|
|
case SPICE_MSGC_INPUTS_MOUSE_POSITION: {
|
|
SpiceMsgcMousePosition *pos = (SpiceMsgcMousePosition *)buf;
|
|
|
|
if (++state->motion_count % SPICE_INPUT_MOTION_ACK_BUNCH == 0) {
|
|
SpiceDataHeader header;
|
|
|
|
header.serial = ++state->serial;
|
|
header.type = SPICE_MSG_INPUTS_MOUSE_MOTION_ACK;
|
|
header.size = 0;
|
|
header.sub_list = 0;
|
|
if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(SpiceDataHeader))
|
|
!= OUTGOING_OK) {
|
|
red_printf("position ack failed");
|
|
reds_disconnect();
|
|
}
|
|
}
|
|
if (reds->mouse_mode != SPICE_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 SPICE_MSGC_INPUTS_MOUSE_PRESS: {
|
|
SpiceMsgcMousePress *mouse_press = (SpiceMsgcMousePress *)buf;
|
|
int dz = 0;
|
|
if (mouse_press->button == SPICE_MOUSE_BUTTON_UP) {
|
|
dz = -1;
|
|
} else if (mouse_press->button == SPICE_MOUSE_BUTTON_DOWN) {
|
|
dz = 1;
|
|
}
|
|
if (reds->mouse_mode == SPICE_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 SPICE_MSGC_INPUTS_MOUSE_RELEASE: {
|
|
SpiceMsgcMouseRelease *mouse_release = (SpiceMsgcMouseRelease *)buf;
|
|
if (reds->mouse_mode == SPICE_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 SPICE_MSGC_INPUTS_KEY_MODIFIERS: {
|
|
SpiceMsgcKeyModifiers *modifiers = (SpiceMsgcKeyModifiers *)buf;
|
|
if (!keyboard) {
|
|
break;
|
|
}
|
|
uint8_t leds = keyboard->get_leds(keyboard);
|
|
if ((modifiers->modifiers & SPICE_SCROLL_LOCK_MODIFIER) !=
|
|
(leds & SPICE_SCROLL_LOCK_MODIFIER)) {
|
|
push_key_scan(SCROLL_LOCK_SCAN_CODE);
|
|
push_key_scan(SCROLL_LOCK_SCAN_CODE | 0x80);
|
|
}
|
|
if ((modifiers->modifiers & SPICE_NUM_LOCK_MODIFIER) != (leds & SPICE_NUM_LOCK_MODIFIER)) {
|
|
push_key_scan(NUM_LOCK_SCAN_CODE);
|
|
push_key_scan(NUM_LOCK_SCAN_CODE | 0x80);
|
|
}
|
|
if ((modifiers->modifiers & SPICE_CAPS_LOCK_MODIFIER) != (leds & SPICE_CAPS_LOCK_MODIFIER)) {
|
|
push_key_scan(CAPS_LOCK_SCAN_CODE);
|
|
push_key_scan(CAPS_LOCK_SCAN_CODE | 0x80);
|
|
}
|
|
activate_modifiers_watch();
|
|
break;
|
|
}
|
|
case SPICE_MSGC_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;
|
|
SpiceDataHeader header;
|
|
SpiceMsgMigrate migrate;
|
|
|
|
red_printf("");
|
|
header.serial = ++state->serial;
|
|
header.type = SPICE_MSG_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);
|
|
|
|
inputs_state = spice_new0(InputsState, 1);
|
|
|
|
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));
|
|
}
|
|
|
|
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);
|
|
|
|
SpiceDataHeader header;
|
|
SpiceMsgInputsInit inputs_init;
|
|
header.serial = ++inputs_state->serial;
|
|
header.type = SPICE_MSG_INPUTS_INIT;
|
|
header.size = sizeof(SpiceMsgInputsInit);
|
|
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(SpiceDataHeader)) != OUTGOING_OK ||
|
|
outgoing_write(inputs_state->peer, &inputs_state->out_handler, &inputs_init,
|
|
sizeof(SpiceMsgInputsInit)) != OUTGOING_OK) {
|
|
red_printf("failed to send modifiers state");
|
|
reds_disconnect();
|
|
}
|
|
}
|
|
|
|
static void reds_send_keyboard_modifiers(uint8_t modifiers)
|
|
{
|
|
Channel *channel = reds_find_channel(SPICE_CHANNEL_INPUTS, 0);
|
|
InputsState *state;
|
|
|
|
if (!channel || !(state = (InputsState *)channel->data)) {
|
|
return;
|
|
}
|
|
ASSERT(state->peer);
|
|
SpiceDataHeader header;
|
|
SpiceMsgInputsKeyModifiers key_modifiers;
|
|
header.serial = ++state->serial;
|
|
header.type = SPICE_MSG_INPUTS_KEY_MODIFIERS;
|
|
header.size = sizeof(SpiceMsgInputsKeyModifiers);
|
|
header.sub_list = 0;
|
|
key_modifiers.modifiers = modifiers;
|
|
|
|
if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(SpiceDataHeader))
|
|
!= OUTGOING_OK ||
|
|
outgoing_write(state->peer, &state->out_handler, &key_modifiers, sizeof(SpiceMsgInputsKeyModifiers))
|
|
!= OUTGOING_OK) {
|
|
red_printf("failed to send modifiers state");
|
|
reds_disconnect();
|
|
}
|
|
}
|
|
|
|
static void reds_on_keyboard_leds_change(void *opaque, uint8_t leds)
|
|
{
|
|
reds_send_keyboard_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;
|
|
|
|
channel = spice_new0(Channel, 1);
|
|
channel->type = SPICE_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;
|
|
SpiceLinkMess *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, SPICE_LINK_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, SPICE_LINK_ERR_CHANNEL_NOT_AVAILABLE);
|
|
reds_release_link(link);
|
|
return;
|
|
}
|
|
|
|
reds_send_link_result(link, SPICE_LINK_ERR_OK);
|
|
reds_show_new_channel(link);
|
|
if (link_mess->channel_type == SPICE_CHANNEL_INPUTS && !link->peer->ssl) {
|
|
SimpleOutItem *item;
|
|
SpiceMsgNotify *notify;
|
|
char *mess = "keybord channel is unsecure";
|
|
const int mess_len = strlen(mess);
|
|
|
|
LOG_MESSAGE(VD_LOG_WARN, "%s", mess);
|
|
|
|
item = new_simple_out_item(SPICE_MSG_NOTIFY, sizeof(SpiceMsgNotify) + mess_len + 1);
|
|
notify = (SpiceMsgNotify *)item->data;
|
|
notify->time_stamp = get_time_stamp();
|
|
notify->severty = SPICE_NOTIFY_SEVERITY_WARN;
|
|
notify->visibilty = SPICE_NOTIFY_VISIBILITY_HIGH;
|
|
notify->what = SPICE_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[SPICE_MAX_PASSWORD_LENGTH];
|
|
time_t ltime;
|
|
|
|
//todo: use monotonic time
|
|
time(<ime);
|
|
RSA_private_decrypt(link->tiTicketing.rsa_size,
|
|
link->tiTicketing.encrypted_ticket.encrypted_data,
|
|
(unsigned char *)password, link->tiTicketing.rsa, RSA_PKCS1_OAEP_PADDING);
|
|
|
|
if (ticketing_enabled) {
|
|
int expired = !link->link_mess->connection_id && taTicket.expiration_time < ltime;
|
|
char *actual_sever_pass = link->link_mess->connection_id ? reds->taTicket.password :
|
|
taTicket.password;
|
|
if (strlen(actual_sever_pass) == 0) {
|
|
reds_send_link_result(link, SPICE_LINK_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, SPICE_MAX_PASSWORD_LENGTH) != 0) {
|
|
reds_send_link_result(link, SPICE_LINK_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 == SPICE_CHANNEL_MAIN) {
|
|
reds_handle_main_link(link);
|
|
} else {
|
|
reds_handle_other_links(link);
|
|
}
|
|
}
|
|
|
|
static inline void async_read_clear_handlers(AsyncRead *obj)
|
|
{
|
|
if (!obj->active_file_handlers) {
|
|
return;
|
|
}
|
|
obj->active_file_handlers = FALSE;
|
|
core->set_file_handlers(core, obj->peer->socket, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void async_read_handler(void *data)
|
|
{
|
|
AsyncRead *obj = (AsyncRead *)data;
|
|
|
|
for (;;) {
|
|
int n = obj->end - obj->now;
|
|
|
|
ASSERT(n > 0);
|
|
if ((n = obj->peer->cb_read(obj->peer->ctx, obj->now, n)) <= 0) {
|
|
if (n < 0) {
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
if (!obj->active_file_handlers) {
|
|
obj->active_file_handlers = TRUE;
|
|
core->set_file_handlers(core, obj->peer->socket, async_read_handler, NULL,
|
|
obj);
|
|
}
|
|
return;
|
|
case EINTR:
|
|
break;
|
|
default:
|
|
async_read_clear_handlers(obj);
|
|
obj->error(obj->opaque, errno);
|
|
return;
|
|
}
|
|
} else {
|
|
async_read_clear_handlers(obj);
|
|
obj->error(obj->opaque, 0);
|
|
return;
|
|
}
|
|
} else {
|
|
obj->now += n;
|
|
if (obj->now == obj->end) {
|
|
async_read_clear_handlers(obj);
|
|
obj->done(obj->opaque);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int reds_security_check(RedLinkInfo *link)
|
|
{
|
|
ChannelSecurityOptions *security_option = find_channel_security(link->link_mess->channel_type);
|
|
uint32_t security = security_option ? security_option->options : default_channel_security;
|
|
return (link->peer->ssl && (security & SPICE_CHANNEL_SECURITY_SSL)) ||
|
|
(!link->peer->ssl && (security & SPICE_CHANNEL_SECURITY_NONE));
|
|
}
|
|
|
|
static void reds_handle_read_link_done(void *opaque)
|
|
{
|
|
RedLinkInfo *link = (RedLinkInfo *)opaque;
|
|
SpiceLinkMess *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, SPICE_LINK_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, SPICE_LINK_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, SPICE_LINK_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;
|
|
SpiceLinkHeader *header = &link->link_header;
|
|
AsyncRead *obj = &link->asyc_read;
|
|
|
|
if (header->magic != SPICE_MAGIC) {
|
|
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_MAGIC);
|
|
LOG_MESSAGE(VD_LOG_ERROR, "bad magic %u", header->magic);
|
|
reds_release_link(link);
|
|
return;
|
|
}
|
|
|
|
if (header->major_version != SPICE_VERSION_MAJOR) {
|
|
if (header->major_version > 0) {
|
|
reds_send_link_error(link, SPICE_LINK_ERR_VERSION_MISMATCH);
|
|
}
|
|
LOG_MESSAGE(VD_LOG_INFO, "version mismatch client %u.%u server %u.%u",
|
|
header->major_version,
|
|
header->minor_version,
|
|
SPICE_VERSION_MAJOR,
|
|
SPICE_VERSION_MINOR);
|
|
|
|
red_printf("version mismatch");
|
|
reds_release_link(link);
|
|
return;
|
|
}
|
|
|
|
reds->peer_minor_version = header->minor_version;
|
|
|
|
if (header->size < sizeof(SpiceLinkMess)) {
|
|
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
|
|
red_printf("bad size %u", header->size);
|
|
reds_release_link(link);
|
|
return;
|
|
}
|
|
|
|
link->link_mess = spice_malloc(header->size);
|
|
|
|
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 *)((SpiceLinkHeader *)&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 error;
|
|
}
|
|
|
|
if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) == -1) {
|
|
red_printf("accept failed, %s", strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
|
|
red_printf("setsockopt failed, %s", strerror(errno));
|
|
}
|
|
|
|
link = spice_new0(RedLinkInfo, 1);
|
|
peer = spice_new0(RedsStreamContext, 1);
|
|
link->peer = peer;
|
|
peer->socket = socket;
|
|
openssl_init(link);
|
|
|
|
return link;
|
|
|
|
error:
|
|
close(socket);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static RedLinkInfo *reds_accept_connection(int listen_socket)
|
|
{
|
|
RedLinkInfo *link;
|
|
RedsStreamContext *peer;
|
|
|
|
if (!(link = __reds_accept_connection(listen_socket))) {
|
|
return NULL;
|
|
}
|
|
peer = link->peer;
|
|
peer->ctx = (void *)((unsigned long)link->peer->socket);
|
|
peer->cb_read = (int (*)(void *, void *, int))reds_read;
|
|
peer->cb_write = (int (*)(void *, void *, int))reds_write;
|
|
peer->cb_readv = (int (*)(void *, const struct iovec *vector, int count))readv;
|
|
peer->cb_writev = (int (*)(void *, const struct iovec *vector, int count))writev;
|
|
peer->cb_free = (int (*)(RedsStreamContext *))reds_free;
|
|
|
|
return link;
|
|
}
|
|
|
|
static void reds_accept_ssl_connection(void *data)
|
|
{
|
|
RedLinkInfo *link;
|
|
int return_code;
|
|
int ssl_error;
|
|
BIO *sbio;
|
|
|
|
link = __reds_accept_connection(reds->secure_listen_socket);
|
|
if (link == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Handle SSL handshaking
|
|
if (!(sbio = BIO_new_socket(link->peer->socket, BIO_NOCLOSE))) {
|
|
red_printf("could not allocate ssl bio socket");
|
|
goto error;
|
|
}
|
|
|
|
link->peer->ssl = SSL_new(reds->ctx);
|
|
if (!link->peer->ssl) {
|
|
red_printf("could not allocate ssl context");
|
|
BIO_free(sbio);
|
|
goto error;
|
|
}
|
|
|
|
SSL_set_bio(link->peer->ssl, sbio, sbio);
|
|
|
|
link->peer->ctx = (void *)(link->peer->ssl);
|
|
link->peer->cb_write = (int (*)(void *, void *, int))reds_ssl_write;
|
|
link->peer->cb_read = (int (*)(void *, void *, int))reds_ssl_read;
|
|
link->peer->cb_readv = NULL;
|
|
link->peer->cb_writev = reds_ssl_writev;
|
|
link->peer->cb_free = (int (*)(RedsStreamContext *))reds_ssl_free;
|
|
|
|
return_code = SSL_accept(link->peer->ssl);
|
|
if (return_code == 1) {
|
|
reds_handle_new_link(link);
|
|
return;
|
|
}
|
|
|
|
ssl_error = SSL_get_error(link->peer->ssl, return_code);
|
|
if (return_code == -1 && (ssl_error == SSL_ERROR_WANT_READ ||
|
|
ssl_error == SSL_ERROR_WANT_WRITE)) {
|
|
core->set_file_handlers(core, link->peer->socket, reds_handle_ssl_accept,
|
|
reds_handle_ssl_accept, link);
|
|
return;
|
|
}
|
|
|
|
ERR_print_errors_fp(stderr);
|
|
red_printf("SSL_accept failed, error=%d", ssl_error);
|
|
SSL_free(link->peer->ssl);
|
|
|
|
error:
|
|
close(link->peer->socket);
|
|
free(link->peer);
|
|
BN_free(link->tiTicketing.bn);
|
|
free(link);
|
|
}
|
|
|
|
static void reds_accept(void *data)
|
|
{
|
|
RedLinkInfo *link;
|
|
|
|
link = reds_accept_connection(reds->listen_socket);
|
|
if (link == NULL) {
|
|
red_printf("accept failed");
|
|
return;
|
|
}
|
|
reds_handle_new_link(link);
|
|
}
|
|
|
|
static int reds_init_socket(const char *addr, int portnr, int family)
|
|
{
|
|
static const int on=1, off=0;
|
|
struct addrinfo ai,*res,*e;
|
|
char port[33];
|
|
char uaddr[INET6_ADDRSTRLEN+1];
|
|
char uport[33];
|
|
int slisten,rc;
|
|
|
|
memset(&ai,0, sizeof(ai));
|
|
ai.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
|
|
ai.ai_socktype = SOCK_STREAM;
|
|
ai.ai_family = family;
|
|
|
|
snprintf(port, sizeof(port), "%d", portnr);
|
|
rc = getaddrinfo(strlen(addr) ? addr : NULL, port, &ai, &res);
|
|
if (rc != 0) {
|
|
red_error("getaddrinfo(%s,%s): %s\n", addr, port,
|
|
gai_strerror(rc));
|
|
}
|
|
|
|
for (e = res; e != NULL; e = e->ai_next) {
|
|
getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen,
|
|
uaddr,INET6_ADDRSTRLEN, uport,32,
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
slisten = socket(e->ai_family, e->ai_socktype, e->ai_protocol);
|
|
if (slisten < 0) {
|
|
continue;
|
|
}
|
|
|
|
setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,(void*)&on,sizeof(on));
|
|
#ifdef IPV6_V6ONLY
|
|
if (e->ai_family == PF_INET6) {
|
|
/* listen on both ipv4 and ipv6 */
|
|
setsockopt(slisten,IPPROTO_IPV6,IPV6_V6ONLY,(void*)&off,
|
|
sizeof(off));
|
|
}
|
|
#endif
|
|
if (bind(slisten, e->ai_addr, e->ai_addrlen) == 0) {
|
|
goto listen;
|
|
}
|
|
close(slisten);
|
|
}
|
|
red_error("%s: binding socket to %s:%d failed\n", __FUNCTION__,
|
|
addr, portnr);
|
|
freeaddrinfo(res);
|
|
return -1;
|
|
|
|
listen:
|
|
freeaddrinfo(res);
|
|
if (listen(slisten,1) != 0) {
|
|
red_error("%s: listen: %s", __FUNCTION__, strerror(errno));
|
|
close(slisten);
|
|
return -1;
|
|
}
|
|
return slisten;
|
|
}
|
|
|
|
static void reds_init_net()
|
|
{
|
|
if (spice_port != -1) {
|
|
reds->listen_socket = reds_init_socket(spice_addr, spice_port, spice_family);
|
|
if (core->set_file_handlers(core, reds->listen_socket, reds_accept, NULL, NULL)) {
|
|
red_error("set fd handle failed");
|
|
}
|
|
}
|
|
|
|
if (spice_secure_port != -1) {
|
|
reds->secure_listen_socket = reds_init_socket(spice_addr, spice_secure_port,
|
|
spice_family);
|
|
if (core->set_file_handlers(core, reds->secure_listen_socket,
|
|
reds_accept_ssl_connection, NULL, NULL)) {
|
|
red_error("set fd handle failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void load_dh_params(SSL_CTX *ctx, char *file)
|
|
{
|
|
DH *ret = 0;
|
|
BIO *bio;
|
|
|
|
if ((bio = BIO_new_file(file, "r")) == NULL) {
|
|
red_error("Could not open DH file");
|
|
}
|
|
|
|
ret = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
|
|
if (ret == 0) {
|
|
red_error("Could not read DH params");
|
|
}
|
|
|
|
BIO_free(bio);
|
|
|
|
if (SSL_CTX_set_tmp_dh(ctx, ret) < 0) {
|
|
red_error("Could not set DH params");
|
|
}
|
|
}
|
|
|
|
/*The password code is not thread safe*/
|
|
static int ssl_password_cb(char *buf, int size, int flags, void *userdata)
|
|
{
|
|
char *pass = ssl_parameters.keyfile_password;
|
|
if (size < strlen(pass) + 1) {
|
|
return (0);
|
|
}
|
|
|
|
strcpy(buf, pass);
|
|
return (strlen(pass));
|
|
}
|
|
|
|
static unsigned long pthreads_thread_id(void)
|
|
{
|
|
unsigned long ret;
|
|
|
|
ret = (unsigned long)pthread_self();
|
|
return (ret);
|
|
}
|
|
|
|
static void pthreads_locking_callback(int mode, int type, char *file, int line)
|
|
{
|
|
if (mode & CRYPTO_LOCK) {
|
|
pthread_mutex_lock(&(lock_cs[type]));
|
|
lock_count[type]++;
|
|
} else {
|
|
pthread_mutex_unlock(&(lock_cs[type]));
|
|
}
|
|
}
|
|
|
|
static void openssl_thread_setup()
|
|
{
|
|
int i;
|
|
|
|
lock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
|
|
lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
|
|
|
|
for (i = 0; i < CRYPTO_num_locks(); i++) {
|
|
lock_count[i] = 0;
|
|
pthread_mutex_init(&(lock_cs[i]), NULL);
|
|
}
|
|
|
|
CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
|
|
CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
|
|
}
|
|
|
|
static void reds_init_ssl()
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
|
const SSL_METHOD *ssl_method;
|
|
#else
|
|
SSL_METHOD *ssl_method;
|
|
#endif
|
|
int return_code;
|
|
long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
|
|
|
|
/* Global system initialization*/
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
|
|
/* Create our context*/
|
|
ssl_method = TLSv1_method();
|
|
reds->ctx = SSL_CTX_new(ssl_method);
|
|
if (!reds->ctx) {
|
|
red_error("Could not allocate new SSL context");
|
|
}
|
|
|
|
/* Limit connection to TLSv1 only */
|
|
#ifdef SSL_OP_NO_COMPRESSION
|
|
ssl_options |= SSL_OP_NO_COMPRESSION;
|
|
#endif
|
|
SSL_CTX_set_options(reds->ctx, ssl_options);
|
|
|
|
/* Load our keys and certificates*/
|
|
return_code = SSL_CTX_use_certificate_chain_file(reds->ctx, ssl_parameters.certs_file);
|
|
if (return_code != 1) {
|
|
red_error("Could not load certificates from %s", ssl_parameters.certs_file);
|
|
}
|
|
|
|
SSL_CTX_set_default_passwd_cb(reds->ctx, ssl_password_cb);
|
|
|
|
return_code = SSL_CTX_use_PrivateKey_file(reds->ctx, ssl_parameters.private_key_file,
|
|
SSL_FILETYPE_PEM);
|
|
if (return_code != 1) {
|
|
red_error("Could not user private key file");
|
|
}
|
|
|
|
/* Load the CAs we trust*/
|
|
return_code = SSL_CTX_load_verify_locations(reds->ctx, ssl_parameters.ca_certificate_file, 0);
|
|
if (return_code != 1) {
|
|
red_error("Could not use ca file");
|
|
}
|
|
|
|
#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
|
|
SSL_CTX_set_verify_depth(reds->ctx, 1);
|
|
#endif
|
|
|
|
if (strlen(ssl_parameters.dh_key_file) > 0) {
|
|
load_dh_params(reds->ctx, ssl_parameters.dh_key_file);
|
|
}
|
|
|
|
SSL_CTX_set_session_id_context(reds->ctx, (const unsigned char *)"SPICE", 5);
|
|
if (strlen(ssl_parameters.ciphersuite) > 0) {
|
|
SSL_CTX_set_cipher_list(reds->ctx, ssl_parameters.ciphersuite);
|
|
}
|
|
|
|
openssl_thread_setup();
|
|
|
|
#ifndef SSL_OP_NO_COMPRESSION
|
|
STACK *cmp_stack = SSL_COMP_get_compression_methods();
|
|
sk_zero(cmp_stack);
|
|
#endif
|
|
}
|
|
|
|
static void reds_exit()
|
|
{
|
|
if (reds->peer) {
|
|
close(reds->peer->socket);
|
|
}
|
|
#ifdef RED_STATISTICS
|
|
shm_unlink(reds->stat_shm_name);
|
|
free(reds->stat_shm_name);
|
|
#endif
|
|
unsetenv("QEMU_AUDIO_DRV");
|
|
}
|
|
|
|
enum {
|
|
SPICE_OPTION_INVALID,
|
|
SPICE_OPTION_PORT,
|
|
SPICE_OPTION_SPORT,
|
|
SPICE_OPTION_HOST,
|
|
SPICE_OPTION_IMAGE_COMPRESSION,
|
|
SPICE_OPTION_PASSWORD,
|
|
SPICE_OPTION_DISABLE_TICKET,
|
|
SPICE_OPTION_RENDERER,
|
|
SPICE_OPTION_SSLKEY,
|
|
SPICE_OPTION_SSLCERTS,
|
|
SPICE_OPTION_SSLCAFILE,
|
|
SPICE_OPTION_SSLDHFILE,
|
|
SPICE_OPTION_SSLPASSWORD,
|
|
SPICE_OPTION_SSLCIPHERSUITE,
|
|
SPICE_SECURED_CHANNELS,
|
|
SPICE_UNSECURED_CHANNELS,
|
|
SPICE_OPTION_STREAMING_VIDEO,
|
|
SPICE_OPTION_AGENT_MOUSE,
|
|
SPICE_OPTION_PLAYBACK_COMPRESSION,
|
|
};
|
|
|
|
typedef struct OptionsMap {
|
|
const char *name;
|
|
int val;
|
|
} OptionsMap;
|
|
|
|
static int find_option(const char *str, OptionsMap *options_map)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; options_map[i].name != NULL; i++) {
|
|
if (strcmp(str, options_map[i].name) == 0) {
|
|
return options_map[i].val;
|
|
}
|
|
}
|
|
return SPICE_OPTION_INVALID;
|
|
}
|
|
|
|
static void clear_blanks(char **ptr)
|
|
{
|
|
char *str = *ptr;
|
|
while (isspace(*str)) {
|
|
str++;
|
|
}
|
|
while (isspace(str[strlen(str) - 1])) {
|
|
str[strlen(str) - 1] = 0;
|
|
}
|
|
*ptr = str;
|
|
}
|
|
|
|
static int get_option(char **args, char **out_val, OptionsMap *map, char seperator)
|
|
{
|
|
char *p;
|
|
char *next;
|
|
char *val;
|
|
|
|
ASSERT(args && out_val);
|
|
|
|
p = *args;
|
|
if ((next = strchr(p, seperator))) {
|
|
*next = 0;
|
|
*args = next + 1;
|
|
} else {
|
|
*args = NULL;
|
|
}
|
|
|
|
if ((val = strchr(p, '='))) {
|
|
*(val++) = 0;
|
|
clear_blanks(&val);
|
|
*out_val = (strlen(val) == 0) ? NULL : val;
|
|
} else {
|
|
*out_val = NULL;
|
|
}
|
|
|
|
clear_blanks(&p);
|
|
return find_option(p, map);
|
|
}
|
|
|
|
enum {
|
|
SPICE_TICKET_OPTION_INVALID,
|
|
SPICE_TICKET_OPTION_EXPIRATION,
|
|
SPICE_TICKET_OPTION_CONNECTED,
|
|
};
|
|
|
|
static OptionsMap _spice_ticket_options[] = {
|
|
{"expiration", SPICE_TICKET_OPTION_EXPIRATION},
|
|
{"connected", SPICE_TICKET_OPTION_CONNECTED},
|
|
{NULL, 0},
|
|
};
|
|
|
|
static inline void on_activating_ticketing()
|
|
{
|
|
if (!ticketing_enabled && reds->peer) {
|
|
red_printf("disconnecting");
|
|
reds_disconnect();
|
|
}
|
|
}
|
|
|
|
static void reds_reset_ticketing()
|
|
{
|
|
on_activating_ticketing();
|
|
ticketing_enabled = 1;
|
|
taTicket.expiration_time = 0;
|
|
memset(taTicket.password, 0, sizeof(taTicket.password));
|
|
}
|
|
|
|
static void reds_set_ticketing(const char *pass, long expiration)
|
|
{
|
|
ASSERT(expiration >= 0);
|
|
on_activating_ticketing();
|
|
ticketing_enabled = 1;
|
|
if (expiration == 0) {
|
|
taTicket.expiration_time = INT_MAX;
|
|
} else {
|
|
time_t ltime;
|
|
|
|
time(<ime);
|
|
taTicket.expiration_time = ltime + expiration;
|
|
}
|
|
strncpy(taTicket.password, pass, sizeof(taTicket.password));
|
|
}
|
|
|
|
static void reds_do_set_ticket(const char *password, const char *args)
|
|
{
|
|
long expiration = 0;
|
|
char *local_args = NULL;
|
|
const char *term_str = "invalid args";
|
|
int disconnect = FALSE;
|
|
int fail = FALSE;
|
|
|
|
if (!password) {
|
|
term_str = "unexpected NULL password";
|
|
goto error;
|
|
}
|
|
|
|
if (args) {
|
|
char *in_args;
|
|
int option;
|
|
char *val;
|
|
|
|
in_args = local_args = spice_strdup(args);
|
|
do {
|
|
switch (option = get_option(&in_args, &val, _spice_ticket_options, ',')) {
|
|
case SPICE_TICKET_OPTION_EXPIRATION: {
|
|
char *endptr;
|
|
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
expiration = strtol(val, &endptr, 0);
|
|
if (endptr != val + strlen(val) || expiration < 0) {
|
|
term_str = "invalid expiration";
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
case SPICE_TICKET_OPTION_CONNECTED:
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
|
|
if (strcmp(val, "disconnect") == 0) {
|
|
disconnect = TRUE;
|
|
fail = FALSE;
|
|
} else if (strcmp(val, "fail") == 0) {
|
|
fail = TRUE;
|
|
disconnect = FALSE;
|
|
} else if (strcmp(val, "keep") == 0) {
|
|
fail = FALSE;
|
|
disconnect = FALSE;
|
|
} else {
|
|
goto error;
|
|
}
|
|
break;
|
|
default:
|
|
goto error;
|
|
}
|
|
} while (in_args);
|
|
}
|
|
|
|
if (fail && reds->peer) {
|
|
term_str = "Ticket set failed";
|
|
} else {
|
|
if (disconnect) {
|
|
reds_disconnect();
|
|
}
|
|
reds_set_ticketing(password, expiration);
|
|
term_str = "Ticket set successfully";
|
|
}
|
|
core->term_printf(core, "%s\n", term_str);
|
|
free(local_args);
|
|
return;
|
|
|
|
error:
|
|
reds_reset_ticketing();
|
|
core->term_printf(core, "%s\n", term_str);
|
|
free(local_args);
|
|
}
|
|
|
|
static void reds_do_set_ticket_2(const VDICmdArg *args)
|
|
{
|
|
const char *arg2 = NULL;
|
|
|
|
if (!args_is_string(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
if (!args_is_empty(&args[1])) {
|
|
if (!args_is_string(&args[1])) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
arg2 = args[1].string_val;
|
|
}
|
|
|
|
reds_do_set_ticket(args[0].string_val, arg2);
|
|
}
|
|
|
|
static void reds_do_set_ticket64(const char *password64, const char *args)
|
|
{
|
|
char *password;
|
|
|
|
if (!password64) {
|
|
reds_reset_ticketing();
|
|
core->term_printf(core, "unexpected NULL password\n");
|
|
return;
|
|
}
|
|
|
|
if (!(password = base64decode(password64, strlen(password64)))) {
|
|
reds_reset_ticketing();
|
|
core->term_printf(core, "set_ticket64 failed!\n");
|
|
return;
|
|
}
|
|
reds_do_set_ticket(password, args);
|
|
free(password);
|
|
}
|
|
|
|
static void reds_do_set_ticket64_2(const VDICmdArg *args)
|
|
{
|
|
const char *arg2 = NULL;
|
|
|
|
if (!args_is_string(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
if (!args_is_empty(&args[1])) {
|
|
if (!args_is_string(&args[1])) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
arg2 = args[1].string_val;
|
|
}
|
|
|
|
reds_do_set_ticket64(args[0].string_val, arg2);
|
|
}
|
|
|
|
static void reds_do_info_spice()
|
|
{
|
|
core->term_printf(core, "spice info:");
|
|
if (reds->peer) {
|
|
char *ip = NULL;
|
|
struct sockaddr_in sock_addr;
|
|
socklen_t len = sizeof(sock_addr);
|
|
if (getpeername(reds->peer->socket, (struct sockaddr *)&sock_addr, &len) != -1) {
|
|
ip = inet_ntoa(sock_addr.sin_addr);
|
|
}
|
|
core->term_printf(core, " client=%s", ip);
|
|
} else {
|
|
core->term_printf(core, " disconnected");
|
|
}
|
|
core->term_printf(core, " ticketing=%s", ticketing_enabled ? "on" : "off");
|
|
switch (image_compression) {
|
|
case SPICE_IMAGE_COMPRESS_AUTO_GLZ:
|
|
core->term_printf(core, " ic=auto_glz");
|
|
break;
|
|
case SPICE_IMAGE_COMPRESS_AUTO_LZ:
|
|
core->term_printf(core, " ic=auto_lz");
|
|
break;
|
|
case SPICE_IMAGE_COMPRESS_QUIC:
|
|
core->term_printf(core, " ic=quic");
|
|
break;
|
|
case SPICE_IMAGE_COMPRESS_LZ:
|
|
core->term_printf(core, " ic=lz");
|
|
break;
|
|
case SPICE_IMAGE_COMPRESS_GLZ:
|
|
core->term_printf(core, " ic=glz");
|
|
break;
|
|
case SPICE_IMAGE_COMPRESS_OFF:
|
|
core->term_printf(core, " ic=off");
|
|
break;
|
|
case SPICE_IMAGE_COMPRESS_INVALID:
|
|
default:
|
|
core->term_printf(core, " ic=invalid");
|
|
}
|
|
|
|
switch (streaming_video) {
|
|
case STREAM_VIDEO_ALL:
|
|
core->term_printf(core, " sv=all");
|
|
break;
|
|
case STREAM_VIDEO_FILTER:
|
|
core->term_printf(core, " sv=filter");
|
|
break;
|
|
case STREAM_VIDEO_OFF:
|
|
core->term_printf(core, " sv=off");
|
|
break;
|
|
case STREAM_VIDEO_INVALID:
|
|
default:
|
|
core->term_printf(core, " sv=invalid");
|
|
|
|
}
|
|
core->term_printf(core, " playback-compression=%s\n",
|
|
snd_get_playback_compression() ? "on" : "off");
|
|
}
|
|
|
|
static void set_image_compression(spice_image_compression_t val)
|
|
{
|
|
if (val == image_compression) {
|
|
return;
|
|
}
|
|
image_compression = val;
|
|
red_dispatcher_on_ic_change();
|
|
}
|
|
|
|
static spice_image_compression_t reds_get_image_compression(const char *val)
|
|
{
|
|
if ((strcmp(val, "on") == 0) || (strcmp(val, "auto_glz") == 0)) {
|
|
return SPICE_IMAGE_COMPRESS_AUTO_GLZ;
|
|
} else if (strcmp(val, "auto_lz") == 0) {
|
|
return SPICE_IMAGE_COMPRESS_AUTO_LZ;
|
|
} else if (strcmp(val, "quic") == 0) {
|
|
return SPICE_IMAGE_COMPRESS_QUIC;
|
|
} else if (strcmp(val, "glz") == 0) {
|
|
return SPICE_IMAGE_COMPRESS_GLZ;
|
|
} else if (strcmp(val, "lz") == 0) {
|
|
return SPICE_IMAGE_COMPRESS_LZ;
|
|
} else if (strcmp(val, "off") == 0) {
|
|
return SPICE_IMAGE_COMPRESS_OFF;
|
|
}
|
|
return SPICE_IMAGE_COMPRESS_INVALID;
|
|
}
|
|
|
|
static void reds_do_set_image_compression(const char *val)
|
|
{
|
|
spice_image_compression_t real_val = reds_get_image_compression(val);
|
|
if (real_val == SPICE_IMAGE_COMPRESS_INVALID) {
|
|
core->term_printf(core, "bad image compression arg\n");
|
|
return;
|
|
}
|
|
set_image_compression(real_val);
|
|
}
|
|
|
|
static void reds_do_set_image_compression_2(const VDICmdArg *args)
|
|
{
|
|
if (!args_is_string(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
reds_do_set_image_compression(args[0].string_val);
|
|
}
|
|
|
|
static int reds_get_streaming_video(const char *val)
|
|
{
|
|
if (strcmp(val, "on") == 0) {
|
|
return STREAM_VIDEO_FILTER;
|
|
} else if (strcmp(val, "filter") == 0) {
|
|
return STREAM_VIDEO_FILTER;
|
|
} else if (strcmp(val, "all") == 0) {
|
|
return STREAM_VIDEO_ALL;
|
|
} else if (strcmp(val, "off") == 0){
|
|
return STREAM_VIDEO_OFF;
|
|
} else {
|
|
return STREAM_VIDEO_INVALID;
|
|
}
|
|
}
|
|
|
|
static void reds_do_set_streaming_video(const char *val)
|
|
{
|
|
uint32_t new_val = reds_get_streaming_video(val);
|
|
if (new_val == STREAM_VIDEO_INVALID) {
|
|
core->term_printf(core, "bad streaming video arg\n");
|
|
return;
|
|
}
|
|
|
|
if (new_val == streaming_video) {
|
|
return;
|
|
}
|
|
streaming_video = new_val;
|
|
red_dispatcher_on_sv_change();
|
|
}
|
|
|
|
static void reds_do_set_streaming_video_2(const VDICmdArg *args)
|
|
{
|
|
if (!args_is_string(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
reds_do_set_streaming_video(args[0].string_val);
|
|
}
|
|
|
|
static void reds_do_set_agent_mouse(const char *val)
|
|
{
|
|
int new_val;
|
|
if (strcmp(val, "on") == 0) {
|
|
new_val = TRUE;
|
|
} else if (strcmp(val, "off") == 0) {
|
|
new_val = FALSE;
|
|
} else {
|
|
core->term_printf(core, "bad agent mouse arg\n");
|
|
return;
|
|
}
|
|
if (new_val == agent_mouse) {
|
|
return;
|
|
}
|
|
agent_mouse = new_val;
|
|
reds_update_mouse_mode();
|
|
}
|
|
|
|
static void reds_do_set_agent_mouse_2(const VDICmdArg *args)
|
|
{
|
|
if (!args_is_string(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
reds_do_set_agent_mouse(args[0].string_val);
|
|
}
|
|
|
|
static void reds_do_set_playback_compression(const char *val)
|
|
{
|
|
int on;
|
|
if (strcmp(val, "on") == 0) {
|
|
on = TRUE;
|
|
} else if (strcmp(val, "off") == 0) {
|
|
on = FALSE;
|
|
} else {
|
|
core->term_printf(core, "bad playback compression arg\n");
|
|
return;
|
|
}
|
|
snd_set_playback_compression(on);
|
|
}
|
|
|
|
static void reds_do_set_playback_compression_2(const VDICmdArg *args)
|
|
{
|
|
if (!args_is_string(args)) {
|
|
red_printf("invalid args");
|
|
return;
|
|
}
|
|
|
|
reds_do_set_playback_compression(args[0].string_val);
|
|
}
|
|
|
|
static OptionsMap _spice_options[] = {
|
|
{"port", SPICE_OPTION_PORT},
|
|
{"sport", SPICE_OPTION_SPORT},
|
|
{"host", SPICE_OPTION_HOST},
|
|
{"ic", SPICE_OPTION_IMAGE_COMPRESSION},
|
|
{"password", SPICE_OPTION_PASSWORD},
|
|
{"disable-ticketing", SPICE_OPTION_DISABLE_TICKET},
|
|
{"renderer", SPICE_OPTION_RENDERER},
|
|
{"sslkey", SPICE_OPTION_SSLKEY},
|
|
{"sslcert", SPICE_OPTION_SSLCERTS},
|
|
{"sslcafile", SPICE_OPTION_SSLCAFILE},
|
|
{"ssldhfile", SPICE_OPTION_SSLDHFILE},
|
|
{"sslpassword", SPICE_OPTION_SSLPASSWORD},
|
|
{"sslciphersuite", SPICE_OPTION_SSLCIPHERSUITE},
|
|
{"secure-channels", SPICE_SECURED_CHANNELS},
|
|
{"unsecure-channels", SPICE_UNSECURED_CHANNELS},
|
|
{"sv", SPICE_OPTION_STREAMING_VIDEO},
|
|
{"agent-mouse", SPICE_OPTION_AGENT_MOUSE},
|
|
{"playback-compression", SPICE_OPTION_PLAYBACK_COMPRESSION},
|
|
{NULL, 0},
|
|
};
|
|
|
|
static OptionsMap _channel_map[] = {
|
|
{"all", SPICE_CHANNEL_NAME_ALL},
|
|
{"main", SPICE_CHANNEL_NAME_MAIN},
|
|
{"display", SPICE_CHANNEL_NAME_DISPLAY},
|
|
{"inputs", SPICE_CHANNEL_NAME_INPUTS},
|
|
{"cursor", SPICE_CHANNEL_NAME_CURSOR},
|
|
{"playback", SPICE_CHANNEL_NAME_PLAYBACK},
|
|
{"record", SPICE_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 = spice_new(ChannelSecurityOptions, 1);
|
|
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;
|
|
int channel_name;
|
|
char *str;
|
|
char *val;
|
|
int all = 0;
|
|
int specific = 0;
|
|
|
|
local_str = spice_strdup(channels);
|
|
str = local_str;
|
|
do {
|
|
switch (channel_name = get_option(&str, &val, _channel_map, '+')) {
|
|
case SPICE_CHANNEL_NAME_ALL:
|
|
all++;
|
|
break;
|
|
case SPICE_CHANNEL_NAME_MAIN:
|
|
specific++;
|
|
set_one_channel_security(SPICE_CHANNEL_MAIN, security);
|
|
break;
|
|
case SPICE_CHANNEL_NAME_DISPLAY:
|
|
specific++;
|
|
set_one_channel_security(SPICE_CHANNEL_DISPLAY, security);
|
|
break;
|
|
case SPICE_CHANNEL_NAME_INPUTS:
|
|
specific++;
|
|
set_one_channel_security(SPICE_CHANNEL_INPUTS, security);
|
|
break;
|
|
case SPICE_CHANNEL_NAME_CURSOR:
|
|
specific++;
|
|
set_one_channel_security(SPICE_CHANNEL_CURSOR, security);
|
|
break;
|
|
case SPICE_CHANNEL_NAME_PLAYBACK:
|
|
specific++;
|
|
set_one_channel_security(SPICE_CHANNEL_PLAYBACK, security);
|
|
break;
|
|
case SPICE_CHANNEL_NAME_RECORD:
|
|
specific++;
|
|
set_one_channel_security(SPICE_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 = spice_strdup(in_args);
|
|
|
|
args = local_args;
|
|
do {
|
|
switch (option = get_option(&args, &val, _spice_options, ',')) {
|
|
case SPICE_OPTION_PORT: {
|
|
char *endptr;
|
|
long int port;
|
|
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
port = strtol(val, &endptr, 0);
|
|
if (endptr != val + strlen(val) || port < 0 || port > 0xffff) {
|
|
goto error;
|
|
}
|
|
spice_port = port;
|
|
break;
|
|
}
|
|
case SPICE_OPTION_SPORT: {
|
|
char *endptr;
|
|
long int port;
|
|
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
port = strtol(val, &endptr, 0);
|
|
if (endptr != val + strlen(val) || port < 0 || port > 0xffff) {
|
|
goto error;
|
|
}
|
|
|
|
ssl_port = TRUE;
|
|
spice_secure_port = port;
|
|
break;
|
|
}
|
|
case SPICE_OPTION_HOST: {
|
|
if (val) {
|
|
strncpy(spice_addr, val, sizeof(spice_addr));
|
|
/* force ipv4 here for backward compatibility */
|
|
spice_family = PF_INET;
|
|
}
|
|
break;
|
|
}
|
|
case SPICE_OPTION_IMAGE_COMPRESSION:
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
image_compression = reds_get_image_compression(val);
|
|
if (image_compression == SPICE_IMAGE_COMPRESS_INVALID) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case SPICE_OPTION_PASSWORD:
|
|
ticketing_enabled = 1;
|
|
|
|
if (val) {
|
|
strncpy(taTicket.password, val, sizeof taTicket.password);
|
|
//todo: add expiration option
|
|
taTicket.expiration_time = INT_MAX;
|
|
}
|
|
|
|
break;
|
|
case SPICE_OPTION_DISABLE_TICKET:
|
|
ticketing_enabled = 0;
|
|
break;
|
|
case SPICE_OPTION_RENDERER:
|
|
renderers_opt = TRUE;
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
while (val) {
|
|
char *now = val;
|
|
if ((val = strchr(now, '+'))) {
|
|
*val++ = 0;
|
|
}
|
|
if (!red_dispatcher_add_renderer(now)) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case SPICE_OPTION_SSLCIPHERSUITE:
|
|
ssl_ciphersuite = TRUE;
|
|
|
|
if (val) {
|
|
strncpy(ssl_parameters.ciphersuite, val, sizeof(ssl_parameters.ciphersuite));
|
|
}
|
|
|
|
break;
|
|
case SPICE_OPTION_SSLPASSWORD:
|
|
if (val) {
|
|
strncpy(ssl_parameters.keyfile_password, val,
|
|
sizeof(ssl_parameters.keyfile_password));
|
|
}
|
|
break;
|
|
case SPICE_OPTION_SSLKEY:
|
|
ssl_key = TRUE;
|
|
|
|
if (val) {
|
|
strncpy(ssl_parameters.private_key_file, val,
|
|
sizeof(ssl_parameters.private_key_file));
|
|
}
|
|
break;
|
|
case SPICE_OPTION_SSLCERTS:
|
|
ssl_certs = TRUE;
|
|
|
|
if (val) {
|
|
strncpy(ssl_parameters.certs_file, val, sizeof(ssl_parameters.certs_file));
|
|
}
|
|
break;
|
|
case SPICE_OPTION_SSLCAFILE:
|
|
ssl_cafile = TRUE;
|
|
|
|
if (val) {
|
|
strncpy(ssl_parameters.ca_certificate_file, val,
|
|
sizeof(ssl_parameters.ca_certificate_file));
|
|
}
|
|
break;
|
|
case SPICE_OPTION_SSLDHFILE:
|
|
ssl_dhfile = TRUE;
|
|
|
|
if (val) {
|
|
strncpy(ssl_parameters.dh_key_file, val, sizeof(ssl_parameters.dh_key_file));
|
|
}
|
|
break;
|
|
case SPICE_SECURED_CHANNELS:
|
|
if (!val || !set_channels_security(val, SPICE_CHANNEL_SECURITY_SSL)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case SPICE_UNSECURED_CHANNELS:
|
|
if (!val || !set_channels_security(val, SPICE_CHANNEL_SECURITY_NONE)) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case SPICE_OPTION_STREAMING_VIDEO:
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
streaming_video = reds_get_streaming_video(val);
|
|
if (streaming_video == STREAM_VIDEO_INVALID) {
|
|
goto error;
|
|
}
|
|
break;
|
|
case SPICE_OPTION_PLAYBACK_COMPRESSION:
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
if (strcmp(val, "on") == 0) {
|
|
snd_set_playback_compression(TRUE);
|
|
} else if (strcmp(val, "off") == 0) {
|
|
snd_set_playback_compression(FALSE);
|
|
} else {
|
|
goto error;
|
|
}
|
|
break;
|
|
case SPICE_OPTION_AGENT_MOUSE:
|
|
if (!val) {
|
|
goto error;
|
|
}
|
|
if (strcmp(val, "on") == 0) {
|
|
agent_mouse = TRUE;
|
|
} else if (strcmp(val, "off") == 0) {
|
|
agent_mouse = FALSE;
|
|
} else {
|
|
goto error;
|
|
}
|
|
break;
|
|
default:
|
|
goto error;
|
|
}
|
|
} while (args);
|
|
|
|
if (!renderers_opt && !red_dispatcher_add_renderer("cairo")) {
|
|
goto error;
|
|
}
|
|
|
|
// All SSL parameters should be either on or off.
|
|
if (ssl_port != ssl_key || ssl_key != ssl_certs || ssl_certs != ssl_cafile ||
|
|
ssl_cafile != ssl_dhfile || ssl_dhfile != ssl_ciphersuite) {
|
|
|
|
goto error;
|
|
}
|
|
free(local_args);
|
|
return TRUE;
|
|
|
|
error:
|
|
free(local_args);
|
|
return FALSE;
|
|
}
|
|
|
|
const char *spice_usage_str[] __attribute__ ((visibility ("default"))) = {
|
|
"[port=<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[SPICE_TICKET_PUBKEY_BYTES];
|
|
uint32_t mig_key;
|
|
|
|
char *local_args;
|
|
char *host;
|
|
int port;
|
|
int sport;
|
|
uint16_t cert_pub_key_type;
|
|
uint32_t cert_pub_key_len;
|
|
uint8_t* cert_pub_key;
|
|
} RedsMigSpice;
|
|
|
|
typedef struct RedsMigSpiceMessage {
|
|
uint32_t link_id;
|
|
} RedsMigSpiceMessage;
|
|
|
|
typedef struct RedsMigCertPubKeyInfo {
|
|
uint16_t type;
|
|
uint32_t len;
|
|
} RedsMigCertPubKeyInfo;
|
|
|
|
static int reds_mig_actual_read(RedsMigSpice *s)
|
|
{
|
|
for (;;) {
|
|
uint8_t *buf = s->read.buf;
|
|
uint32_t pos = s->read.end_pos;
|
|
int n;
|
|
n = read(s->fd, buf + pos, s->read.size - pos);
|
|
if (n <= 0) {
|
|
if (n == 0) {
|
|
return -1;
|
|
}
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
return 0;
|
|
case EINTR:
|
|
break;
|
|
case EPIPE:
|
|
return -1;
|
|
default:
|
|
red_printf("%s", strerror(errno));
|
|
return -1;
|
|
}
|
|
} else {
|
|
s->read.end_pos += n;
|
|
if (s->read.end_pos == s->read.size) {
|
|
s->read.handle_data(s);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int reds_mig_actual_write(RedsMigSpice *s)
|
|
{
|
|
if (!s->write.length) {
|
|
return 0;
|
|
}
|
|
|
|
while (s->write.length) {
|
|
int n;
|
|
|
|
n = write(s->fd, s->write.now, s->write.length);
|
|
if (n <= 0) {
|
|
if (n == 0) {
|
|
return -1;
|
|
}
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
return 0;
|
|
case EINTR:
|
|
break;
|
|
case EPIPE:
|
|
return -1;
|
|
default:
|
|
red_printf("%s", strerror(errno));
|
|
return -1;
|
|
}
|
|
} else {
|
|
s->write.now += n;
|
|
s->write.length -= n;
|
|
}
|
|
}
|
|
|
|
s->write.handle_done(s);
|
|
return 0;
|
|
}
|
|
|
|
static void reds_mig_failed(RedsMigSpice *s)
|
|
{
|
|
red_printf("");
|
|
core->set_file_handlers(core, s->fd, NULL, NULL, NULL);
|
|
if (s->local_args) {
|
|
free(s->local_args);
|
|
}
|
|
free(s);
|
|
|
|
reds_mig_disconnect();
|
|
}
|
|
|
|
static void reds_mig_write(void *data)
|
|
{
|
|
RedsMigSpice *s = data;
|
|
|
|
if (reds_mig_actual_write((RedsMigSpice *)data)) {
|
|
red_printf("write error cannot continue spice migration");
|
|
reds_mig_failed(s);
|
|
}
|
|
}
|
|
|
|
static void reds_mig_read(void *data)
|
|
{
|
|
RedsMigSpice *s = data;
|
|
|
|
if (reds_mig_actual_read((RedsMigSpice *)data)) {
|
|
red_printf("read error cannot continue spice migration");
|
|
reds_mig_failed(s);
|
|
}
|
|
}
|
|
|
|
static void reds_mig_continue(RedsMigSpice *s)
|
|
{
|
|
SpiceMsgMainMigrationBegin *migrate;
|
|
SimpleOutItem *item;
|
|
int host_len;
|
|
|
|
red_printf("");
|
|
core->set_file_handlers(core, s->fd, NULL, NULL, NULL);
|
|
host_len = strlen(s->host) + 1;
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_MIGRATE_BEGIN,
|
|
sizeof(SpiceMsgMainMigrationBegin) + host_len + s->cert_pub_key_len);
|
|
migrate = (SpiceMsgMainMigrationBegin *)item->data;
|
|
migrate->port = s->port;
|
|
migrate->sport = s->sport;
|
|
migrate->host_offset = sizeof(SpiceMsgMainMigrationBegin);
|
|
migrate->host_size = host_len;
|
|
migrate->pub_key_type = s->cert_pub_key_type;
|
|
migrate->pub_key_offset = sizeof(SpiceMsgMainMigrationBegin) + host_len;
|
|
migrate->pub_key_size = s->cert_pub_key_len;
|
|
memcpy((uint8_t*)(migrate) + migrate->host_offset , s->host, host_len);
|
|
memcpy((uint8_t*)(migrate) + migrate->pub_key_offset, s->cert_pub_key, s->cert_pub_key_len);
|
|
reds_push_pipe_item(&item->base);
|
|
|
|
free(s->local_args);
|
|
free(s);
|
|
reds->mig_wait_connect = TRUE;
|
|
core->arm_timer(core, reds->mig_timer, MIGRATE_TIMEOUT);
|
|
}
|
|
|
|
static void reds_mig_receive_ack(RedsMigSpice *s)
|
|
{
|
|
s->read.size = sizeof(uint32_t);
|
|
s->read.end_pos = 0;
|
|
s->read.handle_data = reds_mig_continue;
|
|
|
|
core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s);
|
|
}
|
|
|
|
static void reds_mig_send_link_id(RedsMigSpice *s)
|
|
{
|
|
RedsMigSpiceMessage *data = (RedsMigSpiceMessage *)s->write.buf;
|
|
|
|
memcpy(&data->link_id, &reds->link_id, sizeof(reds->link_id));
|
|
|
|
s->write.now = s->write.buf;
|
|
s->write.length = sizeof(RedsMigSpiceMessage);
|
|
s->write.handle_done = reds_mig_receive_ack;
|
|
|
|
core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s);
|
|
}
|
|
|
|
static void reds_mig_send_ticket(RedsMigSpice *s)
|
|
{
|
|
EVP_PKEY *pubkey = NULL;
|
|
BIO *bio_key;
|
|
RSA *rsa;
|
|
int rsa_size = 0;
|
|
|
|
red_printf("");
|
|
|
|
bio_key = BIO_new(BIO_s_mem());
|
|
if (bio_key != NULL) {
|
|
BIO_write(bio_key, s->read.buf, SPICE_TICKET_PUBKEY_BYTES);
|
|
pubkey = d2i_PUBKEY_bio(bio_key, NULL);
|
|
rsa = pubkey->pkey.rsa;
|
|
rsa_size = RSA_size(rsa);
|
|
if (RSA_public_encrypt(strlen(reds->taTicket.password) + 1,
|
|
(unsigned char *)reds->taTicket.password,
|
|
(uint8_t *)(s->write.buf),
|
|
rsa, RSA_PKCS1_OAEP_PADDING) > 0) {
|
|
s->write.length = RSA_size(rsa);
|
|
s->write.now = s->write.buf;
|
|
s->write.handle_done = reds_mig_send_link_id;
|
|
core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s);
|
|
} else {
|
|
reds_mig_failed(s);
|
|
}
|
|
} else {
|
|
reds_mig_failed(s);
|
|
}
|
|
|
|
EVP_PKEY_free(pubkey);
|
|
BIO_free(bio_key);
|
|
}
|
|
|
|
static void reds_mig_receive_cert_public_key(RedsMigSpice *s)
|
|
{
|
|
s->cert_pub_key = spice_memdup(s->read.buf, s->cert_pub_key_len);
|
|
|
|
s->read.size = SPICE_TICKET_PUBKEY_BYTES;
|
|
s->read.end_pos = 0;
|
|
s->read.handle_data = reds_mig_send_ticket;
|
|
|
|
core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s);
|
|
}
|
|
|
|
static void reds_mig_receive_cert_public_key_info(RedsMigSpice *s)
|
|
{
|
|
RedsMigCertPubKeyInfo* pubkey_info = (RedsMigCertPubKeyInfo*)s->read.buf;
|
|
s->cert_pub_key_type = pubkey_info->type;
|
|
s->cert_pub_key_len = pubkey_info->len;
|
|
|
|
if (s->cert_pub_key_len > RECIVE_BUF_SIZE) {
|
|
red_printf("certificate public key length exceeds buffer size");
|
|
reds_mig_failed(s);
|
|
return;
|
|
}
|
|
|
|
if (s->cert_pub_key_len) {
|
|
s->read.size = s->cert_pub_key_len;
|
|
s->read.end_pos = 0;
|
|
s->read.handle_data = reds_mig_receive_cert_public_key;
|
|
} else {
|
|
s->cert_pub_key = NULL;
|
|
s->read.size = SPICE_TICKET_PUBKEY_BYTES;
|
|
s->read.end_pos = 0;
|
|
s->read.handle_data = reds_mig_send_ticket;
|
|
}
|
|
|
|
core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s);
|
|
}
|
|
|
|
static void reds_mig_handle_send_abort_done(RedsMigSpice *s)
|
|
{
|
|
reds_mig_failed(s);
|
|
}
|
|
|
|
static void reds_mig_receive_version(RedsMigSpice *s)
|
|
{
|
|
uint32_t* dest_version;
|
|
uint32_t resault;
|
|
dest_version = (uint32_t*)s->read.buf;
|
|
resault = REDS_MIG_ABORT;
|
|
memcpy(s->write.buf, &resault, sizeof(resault));
|
|
s->write.length = sizeof(resault);
|
|
s->write.now = s->write.buf;
|
|
s->write.handle_done = reds_mig_handle_send_abort_done;
|
|
core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s);
|
|
}
|
|
|
|
static void reds_mig_control(RedsMigSpice *spice_migration)
|
|
{
|
|
uint32_t *control;
|
|
|
|
core->set_file_handlers(core, spice_migration->fd, NULL, NULL, NULL);
|
|
control = (uint32_t *)spice_migration->read.buf;
|
|
|
|
switch (*control) {
|
|
case REDS_MIG_CONTINUE:
|
|
spice_migration->read.size = sizeof(RedsMigCertPubKeyInfo);
|
|
spice_migration->read.end_pos = 0;
|
|
spice_migration->read.handle_data = reds_mig_receive_cert_public_key_info;
|
|
|
|
core->set_file_handlers(core, spice_migration->fd, reds_mig_read,
|
|
NULL, spice_migration);
|
|
break;
|
|
case REDS_MIG_ABORT:
|
|
red_printf("abort");
|
|
reds_mig_failed(spice_migration);
|
|
break;
|
|
case REDS_MIG_DIFF_VERSION:
|
|
red_printf("different versions");
|
|
spice_migration->read.size = sizeof(uint32_t);
|
|
spice_migration->read.end_pos = 0;
|
|
spice_migration->read.handle_data = reds_mig_receive_version;
|
|
|
|
core->set_file_handlers(core, spice_migration->fd, reds_mig_read,
|
|
NULL, spice_migration);
|
|
break;
|
|
default:
|
|
red_printf("invalid control");
|
|
reds_mig_failed(spice_migration);
|
|
}
|
|
}
|
|
|
|
static void reds_mig_receive_control(RedsMigSpice *spice_migration)
|
|
{
|
|
spice_migration->read.size = sizeof(uint32_t);
|
|
spice_migration->read.end_pos = 0;
|
|
spice_migration->read.handle_data = reds_mig_control;
|
|
|
|
core->set_file_handlers(core, spice_migration->fd, reds_mig_read, NULL, spice_migration);
|
|
}
|
|
|
|
static void reds_mig_started(void *opaque, const char *in_args)
|
|
{
|
|
RedsMigSpice *spice_migration = NULL;
|
|
uint32_t *version;
|
|
char *val;
|
|
char *args;
|
|
int option;
|
|
|
|
ASSERT(in_args);
|
|
red_printf("");
|
|
|
|
reds->mig_inprogress = TRUE;
|
|
|
|
if (reds->listen_socket != -1) {
|
|
core->set_file_handlers(core, reds->listen_socket, NULL, NULL, NULL);
|
|
}
|
|
|
|
if (reds->secure_listen_socket != -1) {
|
|
core->set_file_handlers(core, reds->secure_listen_socket, NULL, NULL, NULL);
|
|
}
|
|
|
|
if (reds->peer == NULL) {
|
|
red_printf("not connected to peer");
|
|
goto error;
|
|
}
|
|
|
|
if ((SPICE_VERSION_MAJOR == 1) && (reds->peer_minor_version < 2)) {
|
|
red_printf("minor version mismatch client %u server %u",
|
|
reds->peer_minor_version, SPICE_VERSION_MINOR);
|
|
goto error;
|
|
}
|
|
|
|
spice_migration = spice_new0(RedsMigSpice, 1);
|
|
spice_migration->port = -1;
|
|
spice_migration->sport = -1;
|
|
|
|
spice_migration->local_args = spice_strdup(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;
|
|
SpiceMsgMigrate *migrate;
|
|
|
|
reds->mig_wait_disconnect = TRUE;
|
|
core->arm_timer(core, reds->mig_timer, MIGRATE_TIMEOUT);
|
|
|
|
item = new_simple_out_item(SPICE_MSG_MIGRATE, sizeof(SpiceMsgMigrate));
|
|
migrate = (SpiceMsgMigrate *)item->data;
|
|
migrate->flags = SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER;
|
|
reds_push_pipe_item(&item->base);
|
|
channel = reds->channels;
|
|
while (channel) {
|
|
channel->migrate(channel);
|
|
channel = channel->next;
|
|
}
|
|
} else {
|
|
item = new_simple_out_item(SPICE_MSG_MAIN_MIGRATE_CANCEL, 0);
|
|
reds_push_pipe_item(&item->base);
|
|
reds_mig_cleanup();
|
|
}
|
|
}
|
|
|
|
static int write_all(int fd, const void *in_buf, int len1)
|
|
{
|
|
int ret, len;
|
|
uint8_t *buf = (uint8_t *)in_buf;
|
|
|
|
len = len1;
|
|
while (len > 0) {
|
|
ret = write(fd, buf, len);
|
|
if (ret < 0) {
|
|
if (errno != EINTR && errno != EAGAIN) {
|
|
return -1;
|
|
}
|
|
} else if (ret == 0) {
|
|
break;
|
|
} else {
|
|
buf += ret;
|
|
len -= ret;
|
|
}
|
|
}
|
|
return len1 - len;
|
|
}
|
|
|
|
static int read_all(int fd, void *in_nuf, int lenl)
|
|
{
|
|
int ret, len;
|
|
uint8_t *buf = in_nuf;
|
|
|
|
len = lenl;
|
|
while (len > 0) {
|
|
ret = read(fd, buf, len);
|
|
if (ret < 0) {
|
|
if (errno != EINTR && errno != EAGAIN) {
|
|
return -1;
|
|
}
|
|
} else if (ret == 0) {
|
|
break;
|
|
} else {
|
|
buf += ret;
|
|
len -= ret;
|
|
}
|
|
}
|
|
return lenl - len;
|
|
}
|
|
|
|
static void reds_mig_read_all(int fd, void *buf, int len, const char *name)
|
|
{
|
|
int n = read_all(fd, buf, len);
|
|
if (n != len) {
|
|
red_error("read %s failed, n=%d (%s)", name, n, strerror(errno));
|
|
}
|
|
}
|
|
|
|
static void reds_mig_write_all(int fd, void *buf, int len, const char *name)
|
|
{
|
|
int n = write_all(fd, buf, len);
|
|
if (n != len) {
|
|
red_error("write %s faile, n=%d (%s)", name, n, strerror(errno));
|
|
}
|
|
}
|
|
|
|
static void reds_mig_send_cert_public_key(int fd)
|
|
{
|
|
FILE* cert_file;
|
|
X509* x509;
|
|
EVP_PKEY* pub_key;
|
|
unsigned char* pp = NULL;
|
|
int length;
|
|
BIO* mem_bio;
|
|
RedsMigCertPubKeyInfo pub_key_info_msg;
|
|
|
|
if (spice_secure_port == -1) {
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_INVALID;
|
|
pub_key_info_msg.len = 0;
|
|
reds_mig_write_all(fd, &pub_key_info_msg, sizeof(pub_key_info_msg), "cert public key info");
|
|
return;
|
|
}
|
|
|
|
cert_file = fopen(ssl_parameters.certs_file, "r");
|
|
if (!cert_file) {
|
|
red_error("opening certificate failed");
|
|
}
|
|
|
|
x509 = PEM_read_X509_AUX(cert_file, NULL, NULL, NULL);
|
|
if (!x509) {
|
|
red_error("reading x509 cert failed");
|
|
}
|
|
pub_key = X509_get_pubkey(x509);
|
|
if (!pub_key) {
|
|
red_error("reading public key failed");
|
|
}
|
|
|
|
mem_bio = BIO_new(BIO_s_mem());
|
|
i2d_PUBKEY_bio(mem_bio, pub_key);
|
|
if (BIO_flush(mem_bio) != 1) {
|
|
red_error("bio flush failed");
|
|
}
|
|
length = BIO_get_mem_data(mem_bio, &pp);
|
|
|
|
switch(pub_key->type) {
|
|
case EVP_PKEY_RSA:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_RSA;
|
|
break;
|
|
case EVP_PKEY_RSA2:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_RSA2;
|
|
break;
|
|
case EVP_PKEY_DSA:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_DSA;
|
|
break;
|
|
case EVP_PKEY_DSA1:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_DSA1;
|
|
break;
|
|
case EVP_PKEY_DSA2:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_DSA2;
|
|
break;
|
|
case EVP_PKEY_DSA3:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_DSA3;
|
|
break;
|
|
case EVP_PKEY_DSA4:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_DSA4;
|
|
break;
|
|
case EVP_PKEY_DH:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_DH;
|
|
break;
|
|
case EVP_PKEY_EC:
|
|
pub_key_info_msg.type = SPICE_PUBKEY_TYPE_EC;
|
|
break;
|
|
default:
|
|
red_error("invalid public key type");
|
|
}
|
|
pub_key_info_msg.len = length;
|
|
reds_mig_write_all(fd, &pub_key_info_msg, sizeof(pub_key_info_msg), "cert public key info");
|
|
reds_mig_write_all(fd, pp, length, "cert public key");
|
|
|
|
BIO_free(mem_bio);
|
|
fclose(cert_file);
|
|
EVP_PKEY_free(pub_key);
|
|
X509_free(x509);
|
|
}
|
|
|
|
static void reds_mig_recv(void *opaque, int fd)
|
|
{
|
|
uint32_t ack_message = *(uint32_t *)"ack_";
|
|
char password[SPICE_MAX_PASSWORD_LENGTH];
|
|
RedsMigSpiceMessage mig_message;
|
|
unsigned long f4 = RSA_F4;
|
|
TicketInfo ticketing_info;
|
|
uint32_t version;
|
|
uint32_t resault;
|
|
BIO *bio;
|
|
|
|
BUF_MEM *buff;
|
|
|
|
reds_mig_read_all(fd, &version, sizeof(version), "version");
|
|
// starting from version 3, if the version of the src is bigger
|
|
// than ours, we send our version to the src.
|
|
if (version < REDS_MIG_VERSION) {
|
|
resault = REDS_MIG_ABORT;
|
|
reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
|
|
mig->notifier_done(mig, reds->mig_notifier);
|
|
return;
|
|
} else if (version > REDS_MIG_VERSION) {
|
|
uint32_t src_resault;
|
|
uint32_t self_version = REDS_MIG_VERSION;
|
|
resault = REDS_MIG_DIFF_VERSION;
|
|
reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
|
|
reds_mig_write_all(fd, &self_version, sizeof(self_version), "dest-version");
|
|
reds_mig_read_all(fd, &src_resault, sizeof(src_resault), "src resault");
|
|
|
|
if (src_resault == REDS_MIG_ABORT) {
|
|
red_printf("abort (response to REDS_MIG_DIFF_VERSION)");
|
|
mig->notifier_done(mig, reds->mig_notifier);
|
|
return;
|
|
} else if (src_resault != REDS_MIG_CONTINUE) {
|
|
red_printf("invalid response to REDS_MIG_DIFF_VERSION");
|
|
mig->notifier_done(mig, reds->mig_notifier);
|
|
return;
|
|
}
|
|
} else {
|
|
resault = REDS_MIG_CONTINUE;
|
|
reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
|
|
}
|
|
|
|
reds_mig_send_cert_public_key(fd);
|
|
|
|
ticketing_info.bn = BN_new();
|
|
if (!ticketing_info.bn) {
|
|
red_error("OpenSSL BIGNUMS alloc failed");
|
|
}
|
|
|
|
BN_set_word(ticketing_info.bn, f4);
|
|
if (!(ticketing_info.rsa = RSA_new())) {
|
|
red_error("OpenSSL RSA alloc failed");
|
|
}
|
|
|
|
RSA_generate_key_ex(ticketing_info.rsa, SPICE_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, SPICE_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_keyboard_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()
|
|
{
|
|
SpiceMsgMainMultiMediaTime *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(SPICE_MSG_MAIN_MULTI_MEDIA_TIME, sizeof(SpiceMsgMainMultiMediaTime)))) {
|
|
red_printf("alloc item failed");
|
|
reds_disconnect();
|
|
return;
|
|
}
|
|
time_mes = (SpiceMsgMainMultiMediaTime *)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|filter|all|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_action_commands_2(QTerm2Interface *mon)
|
|
{
|
|
VDIArgDescriptor s[] = {
|
|
{ "arg1", ARG_TYPE_STRING, FALSE},
|
|
{ NULL, 0, 0},
|
|
};
|
|
|
|
VDIArgDescriptor empty[] = {
|
|
{ NULL, 0, 0}
|
|
};
|
|
|
|
VDIArgDescriptor s_s_o[] = {
|
|
{ "arg1", ARG_TYPE_STRING, FALSE},
|
|
{ "arg2", ARG_TYPE_STRING, TRUE},
|
|
{ NULL, 0, 0}
|
|
};
|
|
|
|
VDIArgDescriptor s_o_i_o[] = {
|
|
{ "arg1", ARG_TYPE_STRING, TRUE},
|
|
{ "arg2", ARG_TYPE_INT, TRUE},
|
|
{ NULL, 0, 0}
|
|
};
|
|
|
|
mon->add_action_command_handler(mon, "spice", "set_image_compression", s,
|
|
reds_do_set_image_compression_2,
|
|
"<[on|auto_glz|auto_lz|quic|glz|lz|off]>",
|
|
"");
|
|
|
|
mon->add_action_command_handler(mon, "spice", "set_streaming_video", s,
|
|
reds_do_set_streaming_video_2,
|
|
"<on|filter|all|off>",
|
|
"");
|
|
|
|
mon->add_action_command_handler(mon, "spice", "set_playback_compression", s,
|
|
reds_do_set_playback_compression_2,
|
|
"<on|off>",
|
|
"");
|
|
|
|
mon->add_action_command_handler(mon, "spice", "set_ticket", s_s_o,
|
|
reds_do_set_ticket_2,
|
|
"<password> [expiration=<seconds>]"
|
|
"[,connected=keep|disconnect|fail]",
|
|
"set the spice connection ticket");
|
|
mon->add_action_command_handler(mon, "spice", "set_ticket64", s_s_o,
|
|
reds_do_set_ticket64_2,
|
|
"<password> [expiration=<seconds>]"
|
|
"[,connected=keep|disconnect|fail]",
|
|
"set the spice connection ticket");
|
|
mon->add_action_command_handler(mon, "spice", "disable_ticketing", empty,
|
|
reds_do_disable_ticketing_2,
|
|
"",
|
|
"entirely disables OTP");
|
|
mon->add_action_command_handler(mon, "spice", "set_agent_mouse", s,
|
|
reds_do_set_agent_mouse_2,
|
|
"<on|off>",
|
|
"");
|
|
#ifdef RED_STATISTICS
|
|
mon->add_action_command_handler(mon, "spice", "reset_stat", empty,
|
|
do_reset_statistics_2,
|
|
"",
|
|
"reset spice statistics");
|
|
mon->add_action_command_handler(mon, "spice", "ping_client", s_o_i_o,
|
|
do_ping_client_2,
|
|
"[on [interval]|off]",
|
|
"ping spice client to measure roundtrip");
|
|
#endif
|
|
}
|
|
|
|
static void add_monitor_info_commands(QTermInterface *mon)
|
|
{
|
|
mon->add_info_command_handler(mon, "spice", "state",
|
|
reds_do_info_spice,
|
|
"show spice state");
|
|
mon->add_info_command_handler(mon, "spice", "ticket",
|
|
reds_do_info_ticket,
|
|
"show ticket");
|
|
#ifdef RED_STATISTICS
|
|
mon->add_info_command_handler(mon, "spice", "stat",
|
|
do_info_statistics,
|
|
"show spice statistics");
|
|
mon->add_info_command_handler(mon, "spice", "rtt_client",
|
|
do_info_rtt_client,
|
|
"show rtt to spice client");
|
|
#endif
|
|
}
|
|
|
|
static void add_monitor_info_commands_2(QTerm2Interface *mon)
|
|
{
|
|
mon->add_info_command_handler(mon, "spice", "state",
|
|
reds_do_info_spice,
|
|
"show spice state");
|
|
mon->add_info_command_handler(mon, "spice", "ticket",
|
|
reds_do_info_ticket,
|
|
"show ticket");
|
|
#ifdef RED_STATISTICS
|
|
mon->add_info_command_handler(mon, "spice", "stat",
|
|
do_info_statistics,
|
|
"show spice statistics");
|
|
mon->add_info_command_handler(mon, "spice", "rtt_client",
|
|
do_info_rtt_client,
|
|
"show rtt to spice client");
|
|
#endif
|
|
}
|
|
|
|
static void attach_to_red_agent(VDIPortInterface *interface)
|
|
{
|
|
VDIPortState *state = &reds->agent_state;
|
|
|
|
vdagent = interface;
|
|
reds_update_mouse_mode();
|
|
if (!reds->peer) {
|
|
return;
|
|
}
|
|
state->plug_ref = vdagent->plug(vdagent, &state->plug);
|
|
reds->agent_state.plug_generation++;
|
|
|
|
if (reds->mig_target) {
|
|
return;
|
|
}
|
|
|
|
reds_send_agent_connected();
|
|
}
|
|
|
|
static void interface_change_notifier(void *opaque, VDInterface *interface,
|
|
VDInterfaceChangeType change)
|
|
{
|
|
if (interface->base_version != VM_INTERFACE_VERSION) {
|
|
red_printf("unsuported base interface version");
|
|
return;
|
|
}
|
|
switch (change) {
|
|
case VD_INTERFACE_ADDING:
|
|
if (strcmp(interface->type, VD_INTERFACE_KEYBOARD) == 0) {
|
|
red_printf("VD_INTERFACE_KEYBOARD");
|
|
if (keyboard) {
|
|
red_printf("already have keyboard");
|
|
return;
|
|
}
|
|
if (interface->major_version != VD_INTERFACE_KEYBOARD_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_KEYBOARD_MINOR) {
|
|
red_printf("unsuported keyboard interface");
|
|
return;
|
|
}
|
|
keyboard = (KeyboardInterface *)interface;
|
|
if (keyboard->register_leds_notifier) {
|
|
if (!keyboard->register_leds_notifier(keyboard, reds_on_keyboard_leds_change, NULL)) {
|
|
red_error("register leds notifier failed");
|
|
}
|
|
}
|
|
} else if (strcmp(interface->type, VD_INTERFACE_MOUSE) == 0) {
|
|
red_printf("VD_INTERFACE_MOUSE");
|
|
if (mouse) {
|
|
red_printf("already have mouse");
|
|
return;
|
|
}
|
|
if (interface->major_version != VD_INTERFACE_MOUSE_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_MOUSE_MINOR) {
|
|
red_printf("unsuported mouse interface");
|
|
return;
|
|
}
|
|
mouse = (MouseInterface *)interface;
|
|
} else if (strcmp(interface->type, VD_INTERFACE_MIGRATION) == 0) {
|
|
red_printf("VD_INTERFACE_MIGRATION");
|
|
if (mig) {
|
|
red_printf("already have migration");
|
|
return;
|
|
}
|
|
if (interface->major_version != VD_INTERFACE_MIGRATION_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_MIGRATION_MINOR) {
|
|
red_printf("unsuported migration interface");
|
|
return;
|
|
}
|
|
mig = (MigrationInterface *)interface;
|
|
reds->mig_notifier = mig->register_notifiers(mig, MIGRATION_NOTIFY_SPICE_KEY,
|
|
reds_mig_started, reds_mig_finished,
|
|
reds_mig_recv, NULL);
|
|
if (reds->mig_notifier == INVALID_VD_OBJECT_REF) {
|
|
red_error("migration register failed");
|
|
}
|
|
} else if (strcmp(interface->type, VD_INTERFACE_QXL) == 0) {
|
|
QXLInterface *qxl_interface;
|
|
|
|
red_printf("VD_INTERFACE_QXL");
|
|
if (interface->major_version != VD_INTERFACE_QXL_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_QXL_MINOR) {
|
|
red_printf("unsuported qxl interface");
|
|
return;
|
|
}
|
|
qxl_interface = (QXLInterface *)interface;
|
|
red_dispatcher_init(qxl_interface);
|
|
} else if (strcmp(interface->type, VD_INTERFACE_QTERM) == 0) {
|
|
static int was_here = FALSE;
|
|
red_printf("VD_INTERFACE_QTERM");
|
|
if (was_here) {
|
|
return;
|
|
}
|
|
was_here = TRUE;
|
|
if (interface->major_version != VD_INTERFACE_QTERM_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_QTERM_MINOR) {
|
|
red_printf("unsuported qterm interface");
|
|
return;
|
|
}
|
|
add_monitor_action_commands((QTermInterface *)interface);
|
|
add_monitor_info_commands((QTermInterface *)interface);
|
|
} else if (strcmp(interface->type, VD_INTERFACE_QTERM2) == 0) {
|
|
static int was_here = FALSE;
|
|
red_printf("VD_INTERFACE_QTERM2");
|
|
if (was_here) {
|
|
return;
|
|
}
|
|
was_here = TRUE;
|
|
if (interface->major_version != VD_INTERFACE_QTERM2_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_QTERM2_MINOR) {
|
|
red_printf("unsuported qterm interface");
|
|
return;
|
|
}
|
|
add_monitor_action_commands_2((QTerm2Interface *)interface);
|
|
add_monitor_info_commands_2((QTerm2Interface *)interface);
|
|
} else if (strcmp(interface->type, VD_INTERFACE_TABLET) == 0) {
|
|
red_printf("VD_INTERFACE_TABLET");
|
|
if (tablet) {
|
|
red_printf("already have tablet");
|
|
return;
|
|
}
|
|
if (interface->major_version != VD_INTERFACE_TABLET_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_TABLET_MINOR) {
|
|
red_printf("unsuported tablet interface");
|
|
return;
|
|
}
|
|
tablet = (TabletInterface *)interface;
|
|
reds_update_mouse_mode();
|
|
if (reds->is_client_mouse_allowed) {
|
|
tablet->set_logical_size(tablet, reds->monitor_mode.x_res,
|
|
reds->monitor_mode.y_res);
|
|
}
|
|
} else if (strcmp(interface->type, VD_INTERFACE_PLAYBACK) == 0) {
|
|
red_printf("VD_INTERFACE_PLAYBACK");
|
|
if (interface->major_version != VD_INTERFACE_PLAYBACK_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_PLAYBACK_MINOR) {
|
|
red_printf("unsuported playback interface");
|
|
return;
|
|
}
|
|
snd_attach_playback((PlaybackInterface *)interface);
|
|
} else if (strcmp(interface->type, VD_INTERFACE_RECORD) == 0) {
|
|
red_printf("VD_INTERFACE_RECORD");
|
|
if (interface->major_version != VD_INTERFACE_RECORD_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_RECORD_MINOR) {
|
|
red_printf("unsuported record interface");
|
|
return;
|
|
}
|
|
snd_attach_record((RecordInterface *)interface);
|
|
} else if (strcmp(interface->type, VD_INTERFACE_VDI_PORT) == 0) {
|
|
red_printf("VD_INTERFACE_VDI_PORT");
|
|
if (vdagent) {
|
|
red_printf("vdi port already attached");
|
|
return;
|
|
}
|
|
if (interface->major_version != VD_INTERFACE_VDI_PORT_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_VDI_PORT_MINOR) {
|
|
red_printf("unsuported vdi port interface");
|
|
return;
|
|
}
|
|
attach_to_red_agent((VDIPortInterface *)interface);
|
|
} else if (strcmp(interface->type, VD_INTERFACE_NET_WIRE) == 0) {
|
|
#ifdef HAVE_SLIRP
|
|
NetWireInterface * net_wire = (NetWireInterface *)interface;
|
|
red_printf("VD_INTERFACE_NET_WIRE");
|
|
if (red_tunnel) {
|
|
red_printf("net wire already attached");
|
|
return;
|
|
}
|
|
if (interface->major_version != VD_INTERFACE_NET_WIRE_MAJOR ||
|
|
interface->minor_version < VD_INTERFACE_NET_WIRE_MINOR) {
|
|
red_printf("unsuported net wire interface");
|
|
return;
|
|
}
|
|
red_tunnel = red_tunnel_attach(core, net_wire);
|
|
#else
|
|
red_printf("unsupported net wire interface");
|
|
#endif
|
|
}
|
|
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 = spice_new0(VDAgentExtBuf, 1);
|
|
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 = spice_new0(VDInternalBuf, 1);
|
|
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 = spice_new0(VDIReadBuf, 1);
|
|
buf->out_item.prepare = reds_prepare_read_buf;
|
|
buf->out_item.release = reds_release_read_buf;
|
|
buf->header.type = SPICE_MSG_MAIN_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;
|
|
|
|
static void do_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;
|
|
}
|
|
reds->listen_socket = -1;
|
|
reds->secure_listen_socket = -1;
|
|
reds->peer = NULL;
|
|
reds->in_handler.handle_message = reds_main_handle_message;
|
|
ring_init(&reds->outgoing.pipe);
|
|
reds->outgoing.vec = reds->outgoing.vec_buf;
|
|
|
|
init_vd_agent_resources();
|
|
|
|
if (!(reds->mig_timer = core->create_timer(core, migrate_timout, NULL))) {
|
|
red_error("migration timer create failed");
|
|
}
|
|
if (!(reds->key_modifiers_timer = core->create_timer(core, key_modifiers_sender, NULL))) {
|
|
red_error("key modifiers timer create failed");
|
|
}
|
|
|
|
if (core->next) {
|
|
while ((interface = core->next(core, interface))) {
|
|
interface_change_notifier(&reds, interface, VD_INTERFACE_ADDING);
|
|
}
|
|
}
|
|
if (core->register_change_notifiers) {
|
|
core->register_change_notifiers(core, &reds, interface_change_notifier);
|
|
}
|
|
|
|
#ifdef RED_STATISTICS
|
|
int shm_name_len = strlen(SPICE_STAT_SHM_NAME) + 20;
|
|
int fd;
|
|
|
|
reds->stat_shm_name = (char *)spice_malloc(shm_name_len);
|
|
snprintf(reds->stat_shm_name, shm_name_len, SPICE_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 == (SpiceStat *)MAP_FAILED) {
|
|
red_error("statistics mmap failed, %s", strerror(errno));
|
|
}
|
|
memset(reds->stat, 0, REDS_STAT_SHM_SIZE);
|
|
reds->stat->magic = SPICE_STAT_MAGIC;
|
|
reds->stat->version = SPICE_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 = SPICE_MOUSE_MODE_SERVER;
|
|
atexit(reds_exit);
|
|
}
|
|
|
|
void __attribute__ ((visibility ("default"))) spice_init(CoreInterface *core_interface)
|
|
{
|
|
spice_server_new();
|
|
do_spice_init(core_interface);
|
|
}
|
|
|
|
/* new interface */
|
|
SpiceServer *spice_server_new(void)
|
|
{
|
|
/* we can't handle multiple instances (yet) */
|
|
ASSERT(reds == NULL);
|
|
|
|
reds = spice_new0(RedsState, 1);
|
|
return reds;
|
|
}
|
|
|
|
int spice_server_init(SpiceServer *s, CoreInterface *core)
|
|
{
|
|
ASSERT(reds == s);
|
|
do_spice_init(core);
|
|
if (default_renderer) {
|
|
red_dispatcher_add_renderer(default_renderer);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void spice_server_destroy(SpiceServer *s)
|
|
{
|
|
ASSERT(reds == s);
|
|
reds_exit();
|
|
}
|
|
|
|
int spice_server_set_port(SpiceServer *s, int port)
|
|
{
|
|
ASSERT(reds == s);
|
|
if (port < 0 || port > 0xffff) {
|
|
return -1;
|
|
}
|
|
spice_port = port;
|
|
return 0;
|
|
}
|
|
|
|
void spice_server_set_addr(SpiceServer *s, const char *addr, int flags)
|
|
{
|
|
ASSERT(reds == s);
|
|
strncpy(spice_addr, addr, sizeof(spice_addr));
|
|
if (flags & SPICE_ADDR_FLAG_IPV4_ONLY) {
|
|
spice_family = PF_INET;
|
|
}
|
|
if (flags & SPICE_ADDR_FLAG_IPV6_ONLY) {
|
|
spice_family = PF_INET6;
|
|
}
|
|
}
|
|
|
|
int spice_server_set_noauth(SpiceServer *s)
|
|
{
|
|
ASSERT(reds == s);
|
|
memset(taTicket.password, 0, sizeof(taTicket.password));
|
|
ticketing_enabled = 0;
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_set_ticket(SpiceServer *s, const char *passwd, int lifetime,
|
|
int fail_if_connected, int disconnect_if_connected)
|
|
{
|
|
ASSERT(reds == s);
|
|
|
|
if (reds->peer) {
|
|
if (fail_if_connected) {
|
|
return -1;
|
|
}
|
|
if (disconnect_if_connected) {
|
|
reds_disconnect();
|
|
}
|
|
}
|
|
|
|
on_activating_ticketing();
|
|
ticketing_enabled = 1;
|
|
if (lifetime == 0) {
|
|
taTicket.expiration_time = INT_MAX;
|
|
} else {
|
|
time_t now = time(NULL);
|
|
taTicket.expiration_time = now + lifetime;
|
|
}
|
|
if (passwd != NULL) {
|
|
strncpy(taTicket.password, passwd, sizeof(taTicket.password));
|
|
} else {
|
|
memset(taTicket.password, 0, sizeof(taTicket.password));
|
|
taTicket.expiration_time = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_set_tls(SpiceServer *s, int port,
|
|
const char *ca_cert_file, const char *certs_file,
|
|
const char *private_key_file, const char *key_passwd,
|
|
const char *dh_key_file, const char *ciphersuite)
|
|
{
|
|
ASSERT(reds == s);
|
|
if (port == 0 || ca_cert_file == NULL || certs_file == NULL ||
|
|
private_key_file == NULL) {
|
|
return -1;
|
|
}
|
|
if (port < 0 || port > 0xffff) {
|
|
return -1;
|
|
}
|
|
memset(&ssl_parameters, 0, sizeof(ssl_parameters));
|
|
|
|
spice_secure_port = port;
|
|
strncpy(ssl_parameters.ca_certificate_file, ca_cert_file,
|
|
sizeof(ssl_parameters.ca_certificate_file)-1);
|
|
strncpy(ssl_parameters.certs_file, certs_file,
|
|
sizeof(ssl_parameters.certs_file)-1);
|
|
strncpy(ssl_parameters.private_key_file, private_key_file,
|
|
sizeof(ssl_parameters.private_key_file)-1);
|
|
|
|
if (key_passwd) {
|
|
strncpy(ssl_parameters.keyfile_password, key_passwd,
|
|
sizeof(ssl_parameters.keyfile_password)-1);
|
|
}
|
|
if (ciphersuite) {
|
|
strncpy(ssl_parameters.ciphersuite, ciphersuite,
|
|
sizeof(ssl_parameters.ciphersuite)-1);
|
|
}
|
|
if (dh_key_file) {
|
|
strncpy(ssl_parameters.dh_key_file, dh_key_file,
|
|
sizeof(ssl_parameters.dh_key_file)-1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_set_image_compression(SpiceServer *s,
|
|
spice_image_compression_t comp)
|
|
{
|
|
ASSERT(reds == s);
|
|
set_image_compression(comp);
|
|
return 0;
|
|
}
|
|
|
|
spice_image_compression_t spice_server_get_image_compression(SpiceServer *s)
|
|
{
|
|
ASSERT(reds == s);
|
|
return image_compression;
|
|
}
|
|
|
|
int spice_server_set_channel_security(SpiceServer *s,
|
|
spice_channel_name_t channel,
|
|
int security)
|
|
{
|
|
ASSERT(reds == s);
|
|
if (channel == SPICE_CHANNEL_NAME_ALL) {
|
|
set_all_channels_security(security);
|
|
} else {
|
|
set_one_channel_security(channel, security);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_set_mouse_absolute(SpiceServer *s, int absolute)
|
|
{
|
|
uint32_t mode = absolute ? SPICE_MOUSE_MODE_CLIENT : SPICE_MOUSE_MODE_SERVER;
|
|
|
|
ASSERT(reds == s);
|
|
reds_set_mouse_mode(mode);
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_get_sock_info(SpiceServer *s, struct sockaddr *sa, socklen_t *salen)
|
|
{
|
|
ASSERT(reds == s);
|
|
if (!reds->peer) {
|
|
return -1;
|
|
}
|
|
if (getsockname(reds->peer->socket, sa, salen) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_get_peer_info(SpiceServer *s, struct sockaddr *sa, socklen_t *salen)
|
|
{
|
|
ASSERT(reds == s);
|
|
if (!reds->peer) {
|
|
return -1;
|
|
}
|
|
if (getpeername(reds->peer->socket, sa, salen) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_add_renderer(SpiceServer *s, const char *name)
|
|
{
|
|
ASSERT(reds == s);
|
|
if (!red_dispatcher_add_renderer(name)) {
|
|
return -1;
|
|
}
|
|
default_renderer = NULL;
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_add_interface(SpiceServer *s, VDInterface *interface)
|
|
{
|
|
ASSERT(reds == s);
|
|
interface_change_notifier(NULL, interface, VD_INTERFACE_ADDING);
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_remove_interface(SpiceServer *s, VDInterface *interface)
|
|
{
|
|
ASSERT(reds == s);
|
|
interface_change_notifier(NULL, interface, VD_INTERFACE_REMOVING);
|
|
return 0;
|
|
}
|
|
|
|
int spice_server_kbd_leds(SpiceServer *s, KeyboardInterface *kbd, int leds)
|
|
{
|
|
ASSERT(reds == s);
|
|
reds_on_keyboard_leds_change(NULL, leds);
|
|
return 0;
|
|
}
|