/* packet-spice.c * Routines for Spice protocol dissection * Copyright 2011, Yaniv Kaul * Copyright 2013, Jonathon Jongsma * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This code is based on the protocol specification: * http://www.spice-space.org/docs/spice_protocol.pdf * and the source - git://cgit.freedesktop.org/spice/spice-protocol */ #include "config.h" #include #include #include #include #include /* NOTE: * packet-spice.h is auto-generated from a Spice protocol definition by a tool * included in the spice-common repository * (http://cgit.freedesktop.org/spice/spice-common/) * To re-generate this file, run the following command from the root of the * spice-common tree: * python ./spice_codegen.py --generate-wireshark-dissector \ * spice.proto packet-spice.h */ #include "packet-spice.h" #include "packet-main.h" #define SPICE_MAGIC 0x52454451 /* = "REDQ" */ #define SPICE_VERSION_MAJOR_1 1 #define SPICE_VERSION_MINOR_0 0 #define SPICE_VERSION_MAJOR_UNSTABLE 0xfffe #define SPICE_VERSION_MINOR_UNSTABLE 0xffff #define SPICE_TICKET_PUBKEY_BYTES 162 #define SPICE_ALIGN(a, size) (((a) + ((size) - 1)) & ~((size) - 1)) typedef enum { SPICE_LINK_CLIENT, SPICE_LINK_SERVER, SPICE_TICKET_CLIENT, SPICE_TICKET_SERVER, SPICE_CLIENT_AUTH_SELECT, SPICE_SASL_INIT_FROM_SERVER, SPICE_SASL_START_TO_SERVER, SPICE_SASL_START_FROM_SERVER, SPICE_SASL_START_FROM_SERVER_CONT, SPICE_SASL_STEP_TO_SERVER, SPICE_SASL_STEP_FROM_SERVER, SPICE_SASL_STEP_FROM_SERVER_CONT, SPICE_SASL_DATA, SPICE_DATA } spice_session_state_e; void proto_register_spice(void); void proto_reg_handoff_spice(void); static dissector_handle_t spice_handle; #define SPICE_CHANNEL_NONE 0 #define SPICE_FIRST_AVAIL_MESSAGE 101 #define sizeof_SpiceLinkHeader 16 #define sizeof_SpiceDataHeader 18 #define sizeof_SpiceMiniDataHeader 6 enum { SPICE_PLAYBACK_CAP_CELT_0_5_1, SPICE_PLAYBACK_CAP_VOLUME }; enum { SPICE_PLAYBACK_CAP_CELT_0_5_1_MASK = (1 << SPICE_PLAYBACK_CAP_CELT_0_5_1), SPICE_PLAYBACK_CAP_VOLUME_MASK = (1 << SPICE_PLAYBACK_CAP_VOLUME) }; /* main channel */ enum { SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE, SPICE_MAIN_CAP_VM_NAME_UUID, SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS, SPICE_MAIN_CAP_SEAMLESS_MIGRATE }; enum { SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE_MASK = (1 << SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE), SPICE_MAIN_CAP_VM_NAME_UUID_MASK = (1 << SPICE_MAIN_CAP_VM_NAME_UUID), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS_MASK = (1 << SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS), SPICE_MAIN_CAP_SEAMLESS_MIGRATE_MASK = (1 << SPICE_MAIN_CAP_SEAMLESS_MIGRATE) }; /* record channel capabilities - same as playback */ enum { SPICE_RECORD_CAP_CELT_0_5_1, SPICE_RECORD_CAP_VOLUME }; enum { SPICE_RECORD_CAP_CELT_0_5_1_MASK = (1 << SPICE_RECORD_CAP_CELT_0_5_1), SPICE_RECORD_CAP_VOLUME_MASK = (1 << SPICE_RECORD_CAP_VOLUME) }; /* display channel */ enum { SPICE_DISPLAY_CAP_SIZED_STREAM, SPICE_DISPLAY_CAP_MONITORS_CONFIG, SPICE_DISPLAY_CAP_COMPOSITE, SPICE_DISPLAY_CAP_A8_SURFACE }; enum { SPICE_DISPLAY_CAP_SIZED_STREAM_MASK = (1 << SPICE_DISPLAY_CAP_SIZED_STREAM), SPICE_DISPLAY_CAP_MONITORS_CONFIG_MASK = (1 << SPICE_DISPLAY_CAP_MONITORS_CONFIG), SPICE_DISPLAY_CAP_COMPOSITE_MASK = (1 << SPICE_DISPLAY_CAP_COMPOSITE), SPICE_DISPLAY_CAP_A8_SURFACE_MASK = (1 << SPICE_DISPLAY_CAP_A8_SURFACE) }; /* This structure will be tied to each conversation. */ typedef struct { guint32 connection_id; guint32 num_channel_caps; guint32 destport; guint32 client_auth; guint32 server_auth; guint32 auth_selected; spice_session_state_e next_state; guint16 playback_mode; guint8 channel_type; guint8 channel_id; gboolean client_mini_header; gboolean server_mini_header; } spice_conversation_t; typedef struct { spice_session_state_e state; } spice_packet_t; enum { SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION, SPICE_COMMON_CAP_AUTH_SPICE, SPICE_COMMON_CAP_AUTH_SASL, SPICE_COMMON_CAP_MINI_HEADER }; enum { SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION_MASK = (1 << SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION), SPICE_COMMON_CAP_AUTH_SPICE_MASK = (1 << SPICE_COMMON_CAP_AUTH_SPICE), SPICE_COMMON_CAP_AUTH_SASL_MASK = (1 << SPICE_COMMON_CAP_AUTH_SASL), SPICE_COMMON_CAP_MINI_HEADER_MASK = (1 << SPICE_COMMON_CAP_MINI_HEADER) }; static const value_string spice_auth_select_vs[] = { { SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION, "Auth Selection" }, { SPICE_COMMON_CAP_AUTH_SPICE, "Spice" }, { SPICE_COMMON_CAP_AUTH_SASL, "SASL" }, { SPICE_COMMON_CAP_MINI_HEADER, "Mini header" }, { 0, NULL } }; static const value_string spice_sasl_auth_result_vs[] = { { 0, "CONTINUE" }, { 1, "DONE" }, { 0, NULL } }; #define GET_PDU_FROM_OFFSET(OFFSET) if (avail < pdu_len) { \ pinfo->desegment_offset = OFFSET; \ pinfo->desegment_len = pdu_len - avail; \ return avail; \ } static gint ett_spice = -1; static gint ett_link_client = -1; static gint ett_link_server = -1; static gint ett_link_caps = -1; static gint ett_data = -1; static gint ett_message = -1; static gint ett_ticket_client = -1; static gint ett_auth_select_client = -1; static gint ett_ticket_server = -1; static gint ett_common_client_message = -1; static int proto_spice = -1; static int hf_spice_magic = -1; static int hf_major_version = -1; static int hf_minor_version = -1; static int hf_message_size = -1; static int hf_message_type = -1; static int hf_conn_id = -1; static int hf_channel_type = -1; static int hf_channel_id = -1; static int hf_num_common_caps = -1; static int hf_num_channel_caps = -1; static int hf_caps_offset = -1; static int hf_error_code = -1; static int hf_data = -1; static int hf_serial = -1; static int hf_data_size = -1; static int hf_data_sublist = -1; static int hf_link_client = -1; static int hf_link_server = -1; static int hf_ticket_client = -1; static int hf_auth_select_client = -1; static int hf_ticket_server = -1; static int hf_main_cap_semi_migrate = -1; static int hf_main_cap_vm_name_uuid = -1; static int hf_main_cap_agent_connected_tokens = -1; static int hf_main_cap_seamless_migrate = -1; static int hf_inputs_cap = -1; static int hf_cursor_cap = -1; static int hf_common_cap_auth_select = -1; static int hf_common_cap_auth_spice = -1; static int hf_common_cap_auth_sasl = -1; static int hf_common_cap_mini_header = -1; static int hf_spice_sasl_auth_result = -1; static int hf_playback_cap_celt = -1; static int hf_playback_cap_volume = -1; static int hf_record_cap_volume = -1; static int hf_record_cap_celt = -1; static int hf_display_cap_sized_stream = -1; static int hf_display_cap_monitors_config = -1; static int hf_display_cap_composite = -1; static int hf_display_cap_a8_surface = -1; static const gchar* get_message_type_string(const guint16 message_type, const spice_conversation_t *spice_info, const gboolean client_message) { if (message_type < SPICE_FIRST_AVAIL_MESSAGE) { /* this is a common message */ if (client_message) { return val_to_str_const(message_type, spice_msgc_vs, "Unknown client message"); } else { return val_to_str_const(message_type, spice_msg_vs, "Unknown server message"); } } switch (spice_info->channel_type) { case SPICE_CHANNEL_MAIN: if (client_message) { return val_to_str_const(message_type, spice_msgc_main_vs, "Unknown main channel client message"); } else { return val_to_str_const(message_type, spice_msg_main_vs, "Unknown main channel server message"); } break; case SPICE_CHANNEL_DISPLAY: if (client_message) { return val_to_str_const(message_type, spice_msgc_display_vs, "Unknown display channel client message"); } else { return val_to_str_const(message_type, spice_msg_display_vs, "Unknown display channel server message"); } break; case SPICE_CHANNEL_INPUTS: if (client_message) { return val_to_str_const(message_type, spice_msgc_inputs_vs, "Unknown inputs channel client message"); } else { return val_to_str_const(message_type, spice_msg_inputs_vs, "Unknown inputs channel server message"); } break; case SPICE_CHANNEL_CURSOR: if (client_message) { return val_to_str_const(message_type, NULL, "Unknown cursor channel client message"); } else { return val_to_str_const(message_type, spice_msg_cursor_vs, "Unknown cursor channel server message"); } break; case SPICE_CHANNEL_PLAYBACK: return val_to_str_const(message_type, spice_msg_playback_vs, "Unknown playback channel server message"); break; case SPICE_CHANNEL_RECORD: if (client_message) { return val_to_str_const(message_type, spice_msgc_record_vs, "Unknown record channel client message"); } else { return val_to_str_const(message_type, spice_msg_record_vs, "Unknown record channel server message"); } break; case SPICE_CHANNEL_TUNNEL: if (client_message) { return val_to_str_const(message_type, spice_msgc_tunnel_vs, "Unknown tunnel channel client message"); } else { return val_to_str_const(message_type, spice_msg_tunnel_vs, "Unknown tunnel channel server message"); } break; case SPICE_CHANNEL_SMARTCARD: if (client_message) { return val_to_str_const(message_type, spice_msgc_smartcard_vs, "Unknown smartcard channel client message"); } else { return val_to_str_const(message_type, spice_msg_smartcard_vs, "Unknown smartcard channel server message"); } break; case SPICE_CHANNEL_USBREDIR: if (client_message) { const value_string *values = NULL; if (message_type < SPICE_MSG_END_SPICEVMC) values = spice_msg_spicevmc_vs; return val_to_str_const(message_type, values, "Unknown usbredir channel client message"); } else { const value_string *values = NULL; if (message_type < SPICE_MSGC_END_SPICEVMC) values = spice_msgc_spicevmc_vs; return val_to_str_const(message_type, values, "Unknown usbredir channel server message"); } break; default: break; } return "Unknown message"; } static void dissect_spice_mini_data_header(tvbuff_t *tvb, proto_tree *tree, const spice_conversation_t *spice_info, const gboolean client_message, const guint16 message_type, guint32 offset) { proto_item* ti; proto_tree* subtree; if (tree) { ti = proto_tree_add_text(tree, tvb, offset, 2, "Message type: %s (%d)", get_message_type_string(message_type, spice_info, client_message), message_type); subtree = proto_item_add_subtree(ti, ett_common_client_message); proto_tree_add_item(subtree, hf_message_type, tvb, offset, 2, ENC_LITTLE_ENDIAN); offset += 2; proto_tree_add_item(tree, hf_data_size, tvb, offset, 4, ENC_LITTLE_ENDIAN); } } static void dissect_spice_data_header(tvbuff_t *tvb, proto_tree *tree, const spice_conversation_t *spice_info, const gboolean client_message, const guint16 message_type, guint32 *sublist_size, guint32 offset) { proto_item* ti; proto_tree* subtree; *sublist_size = tvb_get_letohl(tvb, offset + 14); if (tree) { proto_tree_add_item(tree, hf_serial, tvb, offset, 8, ENC_LITTLE_ENDIAN); offset += 8; ti = proto_tree_add_text(tree, tvb, offset, 2, "Message type: %s (%d)", get_message_type_string(message_type, spice_info, client_message), message_type); subtree = proto_item_add_subtree(ti, ett_common_client_message); proto_tree_add_item(subtree, hf_message_type, tvb, offset, 2, ENC_LITTLE_ENDIAN); offset += 2; proto_tree_add_item(tree, hf_data_size, tvb, offset, 4, ENC_LITTLE_ENDIAN); offset += 4; proto_tree_add_item(tree, hf_data_sublist, tvb, offset, 4, ENC_LITTLE_ENDIAN); } } static guint32 dissect_spice_data_server_pdu(tvbuff_t *tvb, proto_tree *tree, packet_info *pinfo, spice_conversation_t *spice_info, guint32 offset, const guint32 total_message_size) { proto_item *ti = NULL, *msg_ti=NULL; proto_tree *data_header_tree, *message_tree; guint16 message_type; guint32 message_size, sublist_size, old_offset; guint32 header_size; spice_dissect_func_t message_func; if (spice_info->client_mini_header && spice_info->server_mini_header) { header_size = sizeof_SpiceMiniDataHeader; message_type = tvb_get_letohs(tvb, offset); message_size = tvb_get_letohl(tvb, offset +2); msg_ti = proto_tree_add_text(tree, tvb, offset, 0, "%s (%d bytes)", get_message_type_string(message_type, spice_info, FALSE), message_size + header_size); message_tree = proto_item_add_subtree(msg_ti, ett_message); ti = proto_tree_add_item(message_tree, hf_data, tvb, offset, header_size, ENC_NA); data_header_tree = proto_item_add_subtree(ti, ett_data); dissect_spice_mini_data_header(tvb, data_header_tree, spice_info, FALSE, message_type, offset); proto_item_set_len(msg_ti, message_size + header_size); } else { header_size = sizeof_SpiceDataHeader; message_type = tvb_get_letohs(tvb, offset + 8); message_size = tvb_get_letohl(tvb, offset + 10); msg_ti = proto_tree_add_text(tree, tvb, offset, 0, "%s (%d bytes)", get_message_type_string(message_type, spice_info, FALSE), message_size + header_size); message_tree = proto_item_add_subtree(msg_ti, ett_message); ti = proto_tree_add_item(message_tree, hf_data, tvb, offset, header_size, ENC_NA); data_header_tree = proto_item_add_subtree(ti, ett_data); dissect_spice_data_header(tvb, data_header_tree, spice_info, FALSE, message_type, &sublist_size, offset); } proto_item_set_len(msg_ti, message_size + header_size); offset += header_size; old_offset = offset; col_append_str(pinfo->cinfo, COL_INFO, get_message_type_string(message_type, spice_info, FALSE)); message_func = spice_server_channel_get_dissect(spice_info->channel_type); if (message_func) { GlobalInfo gi; gi.tvb = tvb; gi.pinfo = pinfo; gi.msgtype_item = ti; // TODO ??? gi.message_offset = offset; gi.message_end = offset + message_size; offset = message_func(message_type, &gi, message_tree, offset); } else { proto_tree_add_text(message_tree, tvb, offset, 0, "Unknown server PDU - cannot dissect"); } if ((offset - old_offset) != message_size) { g_warning("dissect_spice_data_server_pdu() - FIXME:message type %s (%u) in packet %d was not fully dissected" " - dissected %d (offset %d [0x%x]), total message size: %d.\r\n", get_message_type_string(message_type, spice_info, FALSE), message_type, pinfo->fd->num, offset - old_offset, offset, offset, message_size + header_size); offset = old_offset + message_size; } return offset; } static guint32 dissect_spice_data_client_pdu(tvbuff_t *tvb, proto_tree *tree, packet_info *pinfo, spice_conversation_t *spice_info, guint32 offset) { proto_item *ti = NULL; proto_tree *data_header_tree; guint16 message_type; guint32 message_size = 0, sublist_size; guint32 header_size; spice_dissect_func_t message_func; if (spice_info->client_mini_header && spice_info->server_mini_header) { header_size = sizeof_SpiceMiniDataHeader; ti = proto_tree_add_item(tree, hf_data, tvb, offset, header_size, ENC_NA); data_header_tree = proto_item_add_subtree(ti, ett_data); message_type = tvb_get_letohs(tvb, offset); message_size = tvb_get_letohl(tvb, offset + 2); dissect_spice_mini_data_header(tvb, data_header_tree, spice_info, TRUE, message_type, offset); } else { header_size = sizeof_SpiceDataHeader; ti = proto_tree_add_item(tree, hf_data, tvb, offset, header_size, ENC_NA); data_header_tree = proto_item_add_subtree(ti, ett_data); message_type = tvb_get_letohs(tvb, offset + 8); message_size = tvb_get_letohl(tvb, offset + 10); dissect_spice_data_header(tvb, data_header_tree, spice_info, TRUE, message_type, &sublist_size, offset); } col_append_str(pinfo->cinfo, COL_INFO, get_message_type_string(message_type, spice_info, TRUE)); offset += header_size; /* TODO: deal with sub-messages list first. As implementation does not uses sub-messsages list yet, */ /* it cannot be implemented in the dissector yet. */ message_func = spice_client_channel_get_dissect(spice_info->channel_type); if (message_func) { GlobalInfo gi; gi.tvb = tvb; gi.pinfo = pinfo; gi.msgtype_item = ti; // TODO ??? gi.message_offset = offset; gi.message_end = offset + message_size; offset = message_func(message_type, &gi, tree, offset); } else { proto_tree_add_text(tree, tvb, offset, 0, "Unknown client PDU - cannot dissect"); } return offset; } static void dissect_spice_link_common_header(tvbuff_t *tvb, proto_tree *tree) { if (tree) { /* dissect common header */ proto_tree_add_item(tree, hf_spice_magic, tvb, 0, 4, ENC_ASCII|ENC_NA); proto_tree_add_item(tree, hf_major_version, tvb, 4, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_minor_version, tvb, 8, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_message_size, tvb, 12, 4, ENC_LITTLE_ENDIAN); } } static void dissect_spice_common_capabilities(tvbuff_t *tvb, proto_tree *tree, guint32 offset, const guint caps_len, spice_conversation_t *spice_info, gboolean is_client) { /* TODO: save common and per-channel capabilities in spice_info ? */ guint i; guint32 val; for(i = 0; i < caps_len; i++) { val = tvb_get_letohl(tvb, offset); switch (i) { case 0: if (is_client) { spice_info->client_auth = val; } else { spice_info->server_auth = val; } proto_tree_add_boolean(tree, hf_common_cap_auth_select, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_common_cap_auth_spice, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_common_cap_auth_sasl, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_common_cap_mini_header, tvb, offset, 4, val); if (val & SPICE_COMMON_CAP_MINI_HEADER_MASK) { if (is_client) { spice_info->client_mini_header = TRUE; } else { spice_info->server_mini_header = TRUE; } } offset += 4; break; default: proto_tree_add_text(tree, tvb, offset, 4, "Unknown common capability"); offset += 4; break; } } } static void dissect_spice_link_capabilities(tvbuff_t *tvb, proto_tree *tree, guint32 offset, const guint caps_len, const spice_conversation_t *spice_info) { /* TODO: save common and per-channel capabilities in spice_info ? */ guint i; guint32 val; for(i = 0; i < caps_len; i++) { val = tvb_get_letohl(tvb, offset); switch (spice_info->channel_type) { case SPICE_CHANNEL_PLAYBACK: switch (i) { case 0: proto_tree_add_boolean(tree, hf_playback_cap_celt, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_playback_cap_volume, tvb, offset, 4, val); break; default: break; } break; case SPICE_CHANNEL_MAIN: switch (i) { case 0: proto_tree_add_boolean(tree, hf_main_cap_semi_migrate, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_main_cap_vm_name_uuid, tvb, offset, 4, val); /*Note: only relevant for client. TODO: dissect only for client */ proto_tree_add_boolean(tree, hf_main_cap_agent_connected_tokens, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_main_cap_seamless_migrate, tvb, offset, 4 ,val); break; default: break; } break; case SPICE_CHANNEL_DISPLAY: switch (i) { case 0: proto_tree_add_boolean(tree, hf_display_cap_sized_stream, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_display_cap_monitors_config, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_display_cap_composite, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_display_cap_a8_surface, tvb, offset, 4, val); break; default: break; } break; case SPICE_CHANNEL_INPUTS: proto_tree_add_item(tree, hf_inputs_cap, tvb, offset, 4, ENC_LITTLE_ENDIAN); break; case SPICE_CHANNEL_CURSOR: proto_tree_add_item(tree, hf_cursor_cap, tvb, offset, 4, ENC_LITTLE_ENDIAN); break; case SPICE_CHANNEL_RECORD: switch (i) { case 0: proto_tree_add_boolean(tree, hf_record_cap_celt, tvb, offset, 4, val); proto_tree_add_boolean(tree, hf_record_cap_volume, tvb, offset, 4, val); break; default: break; } break; default: proto_tree_add_text(tree, tvb, offset, 0, "Unknown channel - cannot dissect"); break; } offset += 4; } } static void dissect_spice_link_client_pdu(tvbuff_t *tvb, proto_tree *tree, spice_conversation_t *spice_info) { guint32 offset; guint32 common_caps_len, channel_caps_len; proto_item *ti = NULL; proto_tree *link_header_tree = NULL; proto_tree *caps_tree = NULL; if (tree) { ti = proto_tree_add_item(tree, hf_link_client, tvb, 0, sizeof_SpiceLinkHeader, ENC_NA); link_header_tree = proto_item_add_subtree(ti, ett_link_client); dissect_spice_link_common_header(tvb, link_header_tree); } offset = sizeof_SpiceLinkHeader; if (spice_info->channel_type == SPICE_CHANNEL_NONE) { spice_info->channel_type = tvb_get_guint8(tvb, offset + 4); } common_caps_len = tvb_get_letohl(tvb, offset + 6); channel_caps_len = tvb_get_letohl(tvb, offset + 10); proto_tree_add_item(tree, hf_conn_id, tvb, offset, 4, ENC_LITTLE_ENDIAN); offset += 4; proto_tree_add_item(tree, hf_channel_type, tvb, offset, 1, ENC_NA); offset += 1; proto_tree_add_item(tree, hf_channel_id, tvb, offset, 1, ENC_NA); offset += 1; proto_tree_add_item(tree, hf_num_common_caps, tvb, offset, 4, ENC_LITTLE_ENDIAN); offset += 4; proto_tree_add_item(tree, hf_num_channel_caps, tvb, offset, 4, ENC_LITTLE_ENDIAN); offset += 4; proto_tree_add_item(tree, hf_caps_offset, tvb, offset, 4, ENC_LITTLE_ENDIAN); offset += 4; if (common_caps_len > 0) { ti = proto_tree_add_text(tree, tvb, offset, common_caps_len * 4, "Client Common Capabilities (%d bytes)", common_caps_len * 4); /* caps_len multiplied by 4 as length is in UINT32 units */ caps_tree = proto_item_add_subtree(ti, ett_link_caps); dissect_spice_common_capabilities(tvb, caps_tree, offset, common_caps_len, spice_info, TRUE); offset += (common_caps_len * 4); } if (channel_caps_len > 0) { ti = proto_tree_add_text(tree, tvb, offset, channel_caps_len * 4, "Client Channel-specific Capabilities (%d bytes)", channel_caps_len * 4); /* caps_len multiplied by 4 as length is in UINT32 units */ caps_tree = proto_item_add_subtree(ti, ett_link_caps); dissect_spice_link_capabilities(tvb, caps_tree, offset, channel_caps_len, spice_info); } } static void dissect_spice_link_server_pdu(tvbuff_t *tvb, proto_tree *tree, spice_conversation_t *spice_info) { guint32 offset; guint32 common_caps_len, channel_caps_len; proto_item *ti = NULL; proto_tree *link_tree = NULL; proto_tree *caps_tree = NULL; if (tree) { ti = proto_tree_add_item(tree, hf_link_server, tvb, 0, sizeof_SpiceLinkHeader, ENC_NA); link_tree = proto_item_add_subtree(ti, ett_link_server); dissect_spice_link_common_header(tvb, link_tree); } offset = sizeof_SpiceLinkHeader; if (tree) { proto_tree_add_item(tree, hf_error_code, tvb, offset, 4, ENC_LITTLE_ENDIAN); proto_tree_add_text(tree, tvb, offset + 4, SPICE_TICKET_PUBKEY_BYTES, "X.509 SubjectPublicKeyInfo (ASN.1)"); proto_tree_add_item(tree, hf_num_common_caps, tvb, offset + 4 + SPICE_TICKET_PUBKEY_BYTES, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_num_channel_caps, tvb, offset + 8 + SPICE_TICKET_PUBKEY_BYTES, 4, ENC_LITTLE_ENDIAN); proto_tree_add_item(tree, hf_caps_offset, tvb, offset + 12 + SPICE_TICKET_PUBKEY_BYTES, 4, ENC_LITTLE_ENDIAN); } common_caps_len = tvb_get_letohl(tvb, offset + 4 + SPICE_TICKET_PUBKEY_BYTES); channel_caps_len = tvb_get_letohl(tvb, offset + 8 + SPICE_TICKET_PUBKEY_BYTES); offset += (int)sizeof_SpiceLinkHeader + SPICE_TICKET_PUBKEY_BYTES; if (common_caps_len > 0) { ti = proto_tree_add_text(tree, tvb, offset, common_caps_len * 4, "Common Capabilities (%d bytes)", common_caps_len * 4); /* caps_len multiplied by 4 as length is in UINT32 units */ caps_tree = proto_item_add_subtree(ti, ett_link_caps); dissect_spice_common_capabilities(tvb, caps_tree, offset, common_caps_len, spice_info, FALSE); offset += (common_caps_len * 4); } if (channel_caps_len > 0) { ti = proto_tree_add_text(tree, tvb, offset, channel_caps_len * 4, "Channel Capabilities (%d bytes)", channel_caps_len * 4); /* caps_len multiplied by 4 as length is in UINT32 units */ caps_tree = proto_item_add_subtree(ti, ett_link_caps); dissect_spice_link_capabilities(tvb, caps_tree, offset, channel_caps_len, spice_info); } } static int dissect_spice(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { conversation_t *conversation; spice_conversation_t *spice_info; spice_packet_t *per_packet_info; guint32 avail; guint32 pdu_len = 0; guint32 offset; proto_item *ti = NULL; proto_tree *spice_tree = NULL; proto_tree *spice_data_tree = NULL; gboolean client_sasl_list = FALSE; gboolean first_record_in_frame; guint8 sasl_auth_result; conversation = find_or_create_conversation(pinfo); spice_info = (spice_conversation_t*)conversation_get_proto_data(conversation, proto_spice); if (!spice_info) { spice_info = wmem_new0(wmem_file_scope(), spice_conversation_t); spice_info->destport = pinfo->destport; spice_info->channel_type = SPICE_CHANNEL_NONE; spice_info->next_state = SPICE_LINK_CLIENT; spice_info->client_auth = 0; spice_info->server_auth = 0; spice_info->playback_mode = SPICE_AUDIO_DATA_MODE_INVALID; spice_info->client_mini_header = FALSE; spice_info->server_mini_header = FALSE; conversation_add_proto_data(conversation, proto_spice, spice_info); conversation_set_dissector(conversation, spice_handle); } per_packet_info = (spice_packet_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_spice, 0); if (!per_packet_info) { per_packet_info = wmem_new(wmem_file_scope(), spice_packet_t); per_packet_info->state = spice_info->next_state; p_add_proto_data(wmem_file_scope(), pinfo, proto_spice, 0, per_packet_info); } col_set_str(pinfo->cinfo, COL_PROTOCOL, "Spice"); col_clear(pinfo->cinfo, COL_INFO); first_record_in_frame = TRUE; switch (per_packet_info->state) { case SPICE_LINK_CLIENT: avail = tvb_reported_length(tvb); pdu_len = sizeof_SpiceLinkHeader; GET_PDU_FROM_OFFSET(0) pdu_len = tvb_get_letohl(tvb, 12) + sizeof_SpiceLinkHeader; GET_PDU_FROM_OFFSET(0) col_set_str(pinfo->cinfo, COL_INFO, "Client link message"); if (tree) { ti = proto_tree_add_item(tree, proto_spice, tvb, 0, pdu_len, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } dissect_spice_link_client_pdu(tvb, spice_tree, spice_info); col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); spice_info->next_state = SPICE_LINK_SERVER; return pdu_len; break; case SPICE_LINK_SERVER: avail = tvb_reported_length(tvb); pdu_len = sizeof_SpiceLinkHeader; GET_PDU_FROM_OFFSET(0) pdu_len = tvb_get_letohl(tvb, 12) + sizeof_SpiceLinkHeader; GET_PDU_FROM_OFFSET(0) col_set_str(pinfo->cinfo, COL_INFO, "Server link message"); col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); if (tree) { ti = proto_tree_add_item(tree, proto_spice, tvb, 0, pdu_len, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } dissect_spice_link_server_pdu(tvb, spice_tree, spice_info); if (!(spice_info->server_auth & SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION_MASK) || !(spice_info->client_auth & SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION_MASK)) { /* Server or clients support spice ticket auth only */ spice_info->next_state = SPICE_TICKET_CLIENT; } else { /* Protocol selection between client and server */ spice_info->next_state = SPICE_CLIENT_AUTH_SELECT; } return pdu_len; break; case SPICE_CLIENT_AUTH_SELECT: if (spice_info->destport != pinfo->destport) { /* ignore anything from the server, wait for data from client */ g_warning("SPICE_CLIENT_AUTH_SELECT: packet from server - expected from client. Packet: %d", pinfo->fd->num); break; } avail = tvb_reported_length(tvb); pdu_len = 4; GET_PDU_FROM_OFFSET(0) col_set_str(pinfo->cinfo, COL_INFO, "Client authentication method selection"); col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); if (tree) { ti = proto_tree_add_item(tree, proto_spice, tvb, 0, 4, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_auth_select_client); proto_tree_add_item(spice_tree, hf_auth_select_client, tvb, 0, 4, ENC_LITTLE_ENDIAN); } spice_info->auth_selected = tvb_get_letohl(tvb, 0); switch (spice_info->auth_selected) { case SPICE_COMMON_CAP_AUTH_SPICE: spice_info->next_state = SPICE_TICKET_CLIENT; break; case SPICE_COMMON_CAP_AUTH_SASL: spice_info->next_state = SPICE_SASL_INIT_FROM_SERVER; break; default: g_warning("unknown authentication selected"); break; } return 4; break; case SPICE_SASL_INIT_FROM_SERVER: offset = 0; avail = tvb_length_remaining(tvb, offset); pdu_len = 4; GET_PDU_FROM_OFFSET(offset) pdu_len = tvb_get_letohl(tvb, offset); /* the length of the following messages */ if (tree && (spice_tree == NULL)) { ti = proto_tree_add_item(tree, proto_spice, tvb, offset, 4, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); proto_tree_add_text(spice_tree, tvb, offset, 4, "SASL message length: %u", pdu_len); pdu_len += 4; GET_PDU_FROM_OFFSET(offset) proto_item_set_len(ti, pdu_len); col_set_str(pinfo->cinfo, COL_INFO, "SASL supported authentication mechanisms (init from server)"); proto_tree_add_text(spice_tree, tvb, offset, 4, "Supported authentication mechanisms list length: %u", pdu_len - 4); offset += 4; proto_tree_add_text(spice_tree, tvb, offset, pdu_len - 4, "Supported authentication mechanisms list: %s", tvb_format_text(tvb, offset, pdu_len - 4)); offset += (pdu_len - 4); spice_info->next_state = SPICE_SASL_START_TO_SERVER; return offset; case SPICE_SASL_START_TO_SERVER: offset = 0; while (offset < tvb_reported_length(tvb)) { avail = tvb_length_remaining(tvb, offset); pdu_len = 4; GET_PDU_FROM_OFFSET(offset) pdu_len = tvb_get_letohl(tvb, offset); /* the length of the following messages */ if (tree && spice_tree == NULL) { ti = proto_tree_add_item(tree, proto_spice, tvb, offset, 4, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); proto_tree_add_text(spice_tree, tvb, offset, 4, "SASL message length: %u", pdu_len); if (pdu_len == 0) { /* meaning, empty PDU - assuming the client_out_list, which may be empty*/ col_set_str(pinfo->cinfo, COL_INFO, "SASL authentication (start to server)"); spice_info->next_state = SPICE_SASL_START_FROM_SERVER; pdu_len = 4; /* only the size field.*/ offset += pdu_len; } else { pdu_len += 4; GET_PDU_FROM_OFFSET(offset) proto_item_set_len(ti, pdu_len); if (client_sasl_list == FALSE) { client_sasl_list = TRUE; col_set_str(pinfo->cinfo, COL_INFO, "Client selected SASL authentication mechanism (start to server)"); proto_tree_add_text(spice_tree, tvb, offset, 4, "Selected authentication mechanism length: %u", pdu_len - 4); offset += 4; proto_tree_add_text(spice_tree, tvb, offset, pdu_len - 4, "Selected authentication mechanism: %s", tvb_format_text(tvb, offset, pdu_len - 4)); } else { /* this is the client out list, ending the start from client message */ col_set_str(pinfo->cinfo, COL_INFO, "Client out mechanism (start to server)"); proto_tree_add_text(spice_tree, tvb, offset, 4, "Client out mechanism length: %u", pdu_len - 4); offset += 4; proto_tree_add_text(spice_tree, tvb, offset, pdu_len - 4, "Selected client out mechanism: %s", tvb_format_text(tvb, offset, pdu_len - 4)); spice_info->next_state = SPICE_SASL_START_FROM_SERVER; } offset += (pdu_len - 4); } } return pdu_len; break; case SPICE_SASL_START_FROM_SERVER: case SPICE_SASL_STEP_FROM_SERVER: offset = 0; while (offset < tvb_reported_length(tvb)) { avail = tvb_length_remaining(tvb, offset); pdu_len = 4; GET_PDU_FROM_OFFSET(offset) pdu_len = tvb_get_letohl(tvb, offset); /* the length of the following messages */ if (tree && spice_tree == NULL) { ti = proto_tree_add_item(tree, proto_spice, tvb, offset, pdu_len + 4, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); if (per_packet_info->state == SPICE_SASL_START_FROM_SERVER) { col_set_str(pinfo->cinfo, COL_INFO, "SASL authentication (start from server)"); } else { col_set_str(pinfo->cinfo, COL_INFO, "SASL authentication (step from server)"); } proto_tree_add_text(spice_tree, tvb, offset, 4, "SASL message length: %u", pdu_len); if (pdu_len == 0) { /* meaning, empty PDU */ offset += 4; /* only the size field.*/ } else { pdu_len += 4; GET_PDU_FROM_OFFSET(offset) offset += 4; proto_tree_add_text(spice_tree, tvb, offset, pdu_len - 4, "SASL authentication data (%u bytes): %s", pdu_len - 4, tvb_format_text(tvb, offset, pdu_len - 4)); offset += (pdu_len - 4); } } if (per_packet_info->state == SPICE_SASL_START_FROM_SERVER) { spice_info->next_state = SPICE_SASL_START_FROM_SERVER_CONT; } else { spice_info->next_state = SPICE_SASL_STEP_FROM_SERVER_CONT; } return pdu_len; break; case SPICE_SASL_START_FROM_SERVER_CONT: case SPICE_SASL_STEP_FROM_SERVER_CONT: offset = 0; avail = tvb_length_remaining(tvb, offset); if (avail >= 1) { if (tree && spice_tree == NULL) { ti = proto_tree_add_item(tree, proto_spice, tvb, offset, 1, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); col_set_str(pinfo->cinfo, COL_INFO, "SASL authentication - result from server"); sasl_auth_result = tvb_get_guint8(tvb, offset); proto_tree_add_item(spice_tree, hf_spice_sasl_auth_result, tvb, offset, 1, ENC_NA); if (per_packet_info->state == SPICE_SASL_START_FROM_SERVER_CONT) { /* if we are in the sasl start, and can continue */ if (sasl_auth_result == 0) { /* 0 = continue */ spice_info->next_state = SPICE_SASL_STEP_TO_SERVER; } else { g_warning("SPICE_SASL_START_FROM_SERVER_CONT and sasl_auth_result is %d, packet %d", sasl_auth_result, pinfo->fd->num); } } else { /* SPICE_SASL_STEP_FROM_SERVER_CONT state. */ spice_info->next_state = SPICE_TICKET_SERVER; } } return 1; break; case SPICE_SASL_STEP_TO_SERVER: offset = 0; while (offset < tvb_reported_length(tvb)) { avail = tvb_length_remaining(tvb, offset); pdu_len = 4; GET_PDU_FROM_OFFSET(offset) pdu_len = tvb_get_letohl(tvb, offset); /* the length of the following messages */ if (tree && spice_tree == NULL) { ti = proto_tree_add_item(tree, proto_spice, tvb, offset, 4, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); proto_tree_add_text(spice_tree, tvb, offset, 4, "SASL message length: %u", pdu_len); if (pdu_len == 0) { /* meaning, empty PDU - assuming the client_out_list, which may be empty*/ col_set_str(pinfo->cinfo, COL_INFO, "SASL authentication from client (step to server)"); spice_info->next_state = SPICE_SASL_STEP_FROM_SERVER; pdu_len = 4; /* only the size field.*/ offset += pdu_len; } else { pdu_len += 4; GET_PDU_FROM_OFFSET(offset) proto_item_set_len(ti, pdu_len); col_set_str(pinfo->cinfo, COL_INFO, "Clientout (step to server)"); proto_tree_add_text(spice_tree, tvb, offset, 4, "clientout length: %u", pdu_len - 4); offset += 4; proto_tree_add_text(spice_tree, tvb, offset, pdu_len - 4, "clientout list: %s", tvb_format_text(tvb, offset, pdu_len - 4)); spice_info->next_state = SPICE_SASL_STEP_FROM_SERVER; offset += (pdu_len - 4); } } return pdu_len; break; case SPICE_SASL_DATA: offset = 0; while (offset < tvb_reported_length(tvb)) { avail = tvb_length_remaining(tvb, offset); pdu_len = 4; GET_PDU_FROM_OFFSET(offset) pdu_len = tvb_get_ntohl(tvb, offset); /* the length of the following messages */ if (tree && spice_tree == NULL) { ti = proto_tree_add_item(tree, proto_spice, tvb, offset, pdu_len, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_spice); } proto_tree_add_text(spice_tree, tvb, offset, 4, "SASL message length: %u", pdu_len); if (pdu_len == 0) { /* meaning, empty PDU */ return 4; /* only the size field.*/ } else { pdu_len += 4; } GET_PDU_FROM_OFFSET(offset) proto_item_set_len(ti, pdu_len); col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s (SASL wrapped)", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); col_set_str(pinfo->cinfo, COL_INFO, "SASL wrapped Spice message"); offset += 4; proto_tree_add_text(spice_tree, tvb, offset, pdu_len - 4, "SASL data (%u bytes)", pdu_len - 4); offset += (pdu_len - 4); } return pdu_len; break; case SPICE_DATA: offset = 0; while (offset < tvb_reported_length(tvb)) { avail = tvb_length_remaining(tvb, offset); if (spice_info->client_mini_header && spice_info->server_mini_header) { pdu_len = sizeof_SpiceMiniDataHeader; GET_PDU_FROM_OFFSET(offset) pdu_len = tvb_get_letohl(tvb, offset + 2); pdu_len += sizeof_SpiceMiniDataHeader; } else { pdu_len = sizeof_SpiceDataHeader; GET_PDU_FROM_OFFSET(offset) pdu_len = tvb_get_letohl(tvb, offset + 14); /* this is actually the sub-message list size */ if (pdu_len == 0) { /* if there are no sub-messages, get the usual message body size. */ /* Note that we do not dissect properly yet sub-messages - but they */ /* are not used in the protcol either */ pdu_len = tvb_get_letohl(tvb, offset + 10); } else { pdu_len = tvb_get_letohl(tvb, offset + 10); } pdu_len += sizeof_SpiceDataHeader; /* +sizeof_SpiceDataHeader since you need to exclude the SPICE */ /* data header, which is sizeof_SpiceDataHeader (18) bytes long) */ } GET_PDU_FROM_OFFSET(offset) col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); if (!first_record_in_frame) { /* if it's not the first dissected PDU, we want in COL_INFO to have: "PDU_type_A, PDU_typeB, PDU_typeC, etc. */ col_append_str(pinfo->cinfo, COL_INFO, ", "); } if (tree && spice_data_tree == NULL) { ti = proto_tree_add_item(tree, proto_spice, tvb, offset, pdu_len, ENC_NA); spice_data_tree = proto_item_add_subtree(ti, ett_data); } if (spice_info->destport == pinfo->destport) { /* client to server traffic */ offset = dissect_spice_data_client_pdu(tvb, spice_data_tree, pinfo, spice_info, offset); } else { /* server to client traffic */ offset = dissect_spice_data_server_pdu(tvb, spice_data_tree, pinfo, spice_info, offset, pdu_len); } first_record_in_frame = FALSE; } return offset; break; case SPICE_TICKET_CLIENT: if (spice_info->destport != pinfo->destport) /* ignore anything from the server, wait for ticket from client */ break; avail = tvb_reported_length(tvb); pdu_len = 128; GET_PDU_FROM_OFFSET(0) col_set_str(pinfo->cinfo, COL_INFO, "Client ticket"); col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); if (tree) { ti = proto_tree_add_item(tree, proto_spice, tvb, 0, 128, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_ticket_client); proto_tree_add_item(spice_tree, hf_ticket_client, tvb, 0, 128, ENC_NA); } spice_info->next_state = SPICE_TICKET_SERVER; return 128; break; case SPICE_TICKET_SERVER: if (spice_info->destport != pinfo->srcport) /* ignore anything from the client, wait for ticket from server */ break; avail = tvb_reported_length(tvb); pdu_len = 4; GET_PDU_FROM_OFFSET(0) col_set_str(pinfo->cinfo, COL_INFO, "Server ticket"); col_add_fstr(pinfo->cinfo, COL_PROTOCOL, "Spice %s", val_to_str_const(spice_info->channel_type,channel_types_vs, "Unknown")); if (tree) { ti = proto_tree_add_item(tree, proto_spice, tvb, 0, 4, ENC_NA); spice_tree = proto_item_add_subtree(ti, ett_ticket_server); proto_tree_add_item(spice_tree, hf_ticket_server, tvb, 0, 4, ENC_LITTLE_ENDIAN); } if (spice_info->auth_selected == SPICE_COMMON_CAP_AUTH_SASL) { spice_info->next_state = SPICE_SASL_DATA; } else { spice_info->next_state = SPICE_DATA; } return pdu_len; break; default: break; } return 0; } static gboolean test_spice_protocol(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { if ((tvb_reported_length(tvb) >= 4) && (tvb_get_ntohl(tvb, 0) == SPICE_MAGIC)) { dissect_spice(tvb, pinfo, tree, NULL); return TRUE; } return FALSE; } /* Register the protocol with Wireshark */ void proto_register_spice2(void) { /* Setup list of header fields */ static hf_register_info hf[] = { { &hf_link_client, { "Link client header", "spice.link_client", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_link_server, { "Link server header", "spice.link_server", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_spice_magic, { "SPICE MAGIC", "spice.magic", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_major_version, { "Protocol major version", "spice.major_version", FT_INT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_minor_version, { "Protocol minor version", "spice.minor_version", FT_INT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_message_size, { "Message size", "spice.message_size", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_message_type, { "Message type", "spice.message_type", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_conn_id, { "Session ID", "spice.conn_id", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, { &hf_channel_type, { "Channel type", "spice.channel_type", FT_UINT8, BASE_DEC, VALS(channel_types_vs), 0x0, NULL, HFILL } }, { &hf_channel_id, { "Channel ID", "spice2.channel_id", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_num_common_caps, { "Number of common capabilities", "spice.num_common_caps", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_num_channel_caps, { "Number of channel capabilities", "spice.num_channel_caps", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_caps_offset, { "Capabilities offset (bytes)", "spice.caps_offset", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_error_code, { "spice ERROR", "spice.error_code", FT_UINT32, BASE_DEC, VALS(spice_link_err_vs), 0x0, NULL, HFILL } }, { &hf_serial, { "Message serial number", "spice.serial", FT_UINT64, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_data, { "Message header", "spice.message_header", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_data_size, { "Message body size (bytes)", "spice.message_size", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_data_sublist, { "Sub-list offset (bytes)", "spice.message_sublist", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_ticket_client, { "Ticket - client", "spice.ticket_client", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_ticket_server, { "Link result", "spice.ticket_server", FT_UINT32, BASE_DEC, VALS(spice_link_err_vs), 0x0, NULL, HFILL } }, { &hf_auth_select_client, { "Authentication selected by client", "spice.auth_select_client", FT_UINT32, BASE_DEC, VALS(spice_auth_select_vs), 0x0, NULL, HFILL } }, { &hf_common_cap_auth_select, { "Auth Selection", "spice.common_cap_auth_select", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION_MASK, NULL, HFILL } }, { &hf_common_cap_auth_spice, { "Auth Spice", "spice.common_cap_auth_spice", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_COMMON_CAP_AUTH_SPICE_MASK, NULL, HFILL } }, { &hf_common_cap_auth_sasl, { "Auth SASL", "spice.common_cap_auth_sasl", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_COMMON_CAP_AUTH_SASL_MASK, NULL, HFILL } }, { &hf_common_cap_mini_header, { "Mini Header", "spice.common_cap_mini_header", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_COMMON_CAP_MINI_HEADER_MASK, NULL, HFILL } }, { &hf_playback_cap_celt, { "CELT 0.5.1 playback channel support", "spice.playback_cap_celt", FT_BOOLEAN, 3, TFS(&tfs_set_notset), SPICE_PLAYBACK_CAP_CELT_0_5_1_MASK, NULL, HFILL } }, { &hf_playback_cap_volume, { "Volume playback channel support", "spice.playback_cap_volume", FT_BOOLEAN, 3, TFS(&tfs_set_notset), SPICE_PLAYBACK_CAP_VOLUME_MASK, NULL, HFILL } }, { &hf_record_cap_volume, { "Volume record channel support", "spice.record_cap_volume", FT_BOOLEAN, 3, TFS(&tfs_set_notset), SPICE_RECORD_CAP_VOLUME_MASK, NULL, HFILL } }, { &hf_record_cap_celt, { "CELT 0.5.1 record channel support", "spice.record_cap_celt", FT_BOOLEAN, 3, TFS(&tfs_set_notset), SPICE_RECORD_CAP_CELT_0_5_1_MASK, NULL, HFILL } }, { &hf_display_cap_sized_stream, { "Sized stream display channel support", "spice.display_cap_sized_stream", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_DISPLAY_CAP_SIZED_STREAM_MASK, NULL, HFILL } }, { &hf_display_cap_monitors_config, { "Monitors configuration display channel support", "spice.display_cap_monitors_config", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_DISPLAY_CAP_MONITORS_CONFIG_MASK, NULL, HFILL } }, { &hf_display_cap_composite, { "Composite capability display channel support", "spice.display_cap_composite", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_DISPLAY_CAP_COMPOSITE_MASK, NULL, HFILL } }, { &hf_display_cap_a8_surface, { "A8 bitmap display channel support", "spice.display_cap_a8_surface", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_DISPLAY_CAP_A8_SURFACE_MASK, NULL, HFILL } }, { &hf_cursor_cap, { "Cursor channel capability", "spice.cursor_cap", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_inputs_cap, { "Inputs channel capability", "spice.inputs_cap", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_main_cap_semi_migrate, { "Semi-seamless migratation capability", "spice.main_cap_semi_migrate", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE_MASK, NULL, HFILL } }, { &hf_main_cap_vm_name_uuid, { "VM name and UUID messages capability", "spice.main_cap_vm_name_uuid", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_MAIN_CAP_VM_NAME_UUID_MASK, NULL, HFILL } }, { &hf_main_cap_agent_connected_tokens, { "Agent connected tokens capability", "spice.main_cap_agent_connected_tokens", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS_MASK, NULL, HFILL } }, { &hf_main_cap_seamless_migrate, { "Seamless migration capability", "spice.main_cap_seamless_migrate", FT_BOOLEAN, 4, TFS(&tfs_set_notset), SPICE_MAIN_CAP_SEAMLESS_MIGRATE_MASK, NULL, HFILL } }, { &hf_spice_sasl_auth_result, { "Authentication result", "spice.sasl_auth_result", FT_UINT8, BASE_DEC, VALS(spice_sasl_auth_result_vs), 0x0, NULL, HFILL } }, }; /* Setup protocol subtree arrays */ static gint *ett[] = { &ett_spice, &ett_link_client, &ett_link_server, &ett_link_caps, &ett_ticket_client, &ett_auth_select_client, &ett_ticket_server, &ett_data, &ett_message, &ett_common_client_message, }; expert_module_t* expert_spice; /* Register the protocol name and description */ proto_spice = proto_register_protocol("Spice2 protocol", "Spice2", "spice2"); /* Required function calls to register the header fields and subtrees */ expert_spice = expert_register_protocol(proto_spice); spice_register_fields(proto_spice, expert_spice); proto_register_field_array(proto_spice, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); } void proto_reg_handoff_spice2(void) { spice_handle = new_create_dissector_handle(dissect_spice, proto_spice); dissector_add_handle("tcp.port", spice_handle); /* for "decode as" */ heur_dissector_add("tcp", test_spice_protocol, proto_spice); } /* * Editor modelines - http://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * vi: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */