spice协议解析

2026-02-02 22:23:597590

Spice协议解析

----------------------ref:

在Protocol.h里定义了所有通道的公共部分

在Enums.h里定义了消息的type

Message.h里定义了消息的格式

一 简介

Spice一共有11个通道,如附录1.1所示,在所有通道中,main通道必须是第一个建立的。客户端使用不同的端口号区分不同的通道,服务端使用同一个端口号。对于所有通道来说,有一些消息是公共的。常用的通道有:a).main通道负责连接建立 b). display channel 负责图像显示 c). inputs channel for 发送鼠标键盘指令 d). cursor channel 接收鼠标指针的位置 e). Playback channel 接收音频流 and f).Record channel 发送音频。Spice协议采用网络字节序。

二 连接建立

对于所有的通道,连接建立的过程都是相似的,每个通道都要单独建立连接。每个通道建立连接时,客户端首先发送client link message,其中包括通道的类型和属性;服务端收到消息后,回复一个server link message。在server link message中有一个public_key字段,用于spice认证。

2.1 连接建立时的公告报头

client link message和server link message拥有相同的数据包头:

1) SPICE MAGIC:32位,固定值,当前版本中为REDQ;

2) 主要版本号:32位,当服务端和客户端的主要版本号一致时,忽略次要版本号;

3) 次要版本号:32位,当服务端和客户端的主要版本号不一致时,使用次要版本号;

4) 数据包长度:32位,除去spice头部外数据包的长度,单位为字节。

2.2 client link

client link message的字段如下:

1) 会话id:32位,主通道第一次建立连接时,该字段的值为0,服务端生成一个会话id,并在spice main init消息中传递给客户端。spice其他通道建立连接时,该字段值为服务端生成的会话id,通过该字段判断通道间是否属于一次会话;

2) 通道类型:8位,1为主通道,2为display通道,3为input通道,4为cursor通道,5为playback通道,6为record通道;

3) 通道id:8位,当一次会话有多个相同类型的通道时,使用通道id区分相同类型的通道;

4) 共有属性数量:32位,属性分为所有通道共有的和该通道专有属性两类;

5) 通道属性数量:32位,通道专有属性的数量;

6) 属性偏移量:32位,单位为字节,从会话id起(除去头部)到第一个属性的偏移量;

7) 共有属性:32位,每个属性占一位,set为1,not set为0,比如有4个属性,第一个和最后一个set,则值为1001(二进制),即0x09;spice的共有属性包括: AUTH_SELECTION, AUTH_SPICE, AUTH_SASL,MINI_HEADER;

8) 通道属性:32位,其他同上。主通道的专有属性包括: SEMI_SEAMLESS_MIGRATE,NAME_AND_UUID,GENT_CONNECTED_TOKENS,SEAMLESS_MIGRATE。

2.3 server link

server link message的字段如下:

1) 错误码:32位,对client link message的回复,错误码为0时,证明连接正确建立。

2) 公钥:1024位,服务端把公钥发送给客户端,客户端使用公钥对密码进行加密,并将结果在spice ticket消息中发送给服务端进行验证。

3) 属性字段的说明参考client link message。

使用ticket消息进行认证

服务端和客户端拥有相同的密码,服务端根据密码生成公钥和私钥,将公钥通过server link message发送给客户端。客户端使用公钥对密码进行加密,并将加密结果通过client ticket发送给服务端。服务端将收到的密码使用私钥进行解密,并与原来的密码进行对比,如果验证通过,将验证通过的结果通过server ticket消息(通过时该消息字段值为0)告知给客户端。

三 公共报头

除了建立连接时的四种消息外(client link message、server link message、ticket、LinkAuthMechanism)外,其他的消息使用一样公共头部,头部包括16位的消息类型和32位的数据包大小。其中,type的取值各通道间是独立的(不同通道间,即使type值相同,可能并不是同一种消息)。

四 主通道

建立连接后,主通道的第一个消息必须是Server INIT。字段里的Supported mouse mode由虚拟机的配置文件指定,服务端鼠标模式为必选模式,客户端鼠标模式为可选模式,客户端鼠标模式可能需要spice agent组件的支持。客户端模式使用鼠标的绝对位置,服务端鼠标模式使用鼠标的相对位置,只在虚拟机窗口范围内移动,主通道负责鼠标模式的控制。

4.1 server init:

1) 会话id:32位,由服务器生成并发送给客户端,客户端在建立其他通道时,使用该会话id;

2) 显示通道数量:32位,期望的显示通道的数量,不能为0;

3) 支持的鼠标模式:32位,值0x01时为只支持服务端鼠标模式,0x03时支持两种模式;

4) 当前的鼠标模式:32位,01为服务端模式,02为客户端模式;

5) 当前时间:32位,当前服务器的时间,用于视频发送时画面和声音的同步。

4.2 attach channels

服务端发送INIT消息后,客户端使用ATTACH CHANNELS消息对其进行回复,该消息类型值为104。

五 显示通道

服务端的显示通道在redworker.c里处理。qxl会将图像渲染的命令(guest系统指定的)传递给服务端,服务端通过渲染树,去除掉中重复的图像数据,并放到一个管道中(管道与客户端一一对应)。每次push pipe时,从管道尾部取出数据,在服务端进行压缩,并生成一个数据包,发送给客户端。客户端在channel_display.c里对显示通道进行处理,图像显示在canavs_base.c里。

客户端发送SPICE_MSGC_DISPLAY_INIT消息指定自己的图像缓冲区大小(单位像素)。客户端发送SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION指定压缩算法,值4是quic算法。服务端发送SPICE_MSG_DISPLAY_INVAL_PALETTE来清空客户端的缓冲区,发送SPICE_MSG_DISPLAY_SURFACE_CREATE建立一个客户端显示区域,包括区域的分辨率和格式。

5.1 client init

client init 指定客户端图像缓冲区大小:

5.2 preferred compression

PREFERRED_COMPRESSION 指定客户端使用的图像压缩算法,4为quic

5.3 surface create

surface create服务端发送,指定surface的大小分辨率格式。

5.4 draw copy

draw copy 负责将图像从guest拷贝到客户端

surface id:32位

RECT:矩阵,包含4个位置信息,左上右下,指定该draw copy消息源图像的位置。当存在rect时,该字段的4个位置信息为所有rect矩阵位置的边界。

spice clip:type为0时没有,为1时有clip,clip的若干个矩阵对整个大矩阵进行切割。

为1时,会有一个rect[]的列表,包括rect的数量和若干个rect。

地址:32位指针地址

Rect:源图像的大小,矩形矩阵,可能与上面的rect字段大小相等,不等时进行缩放。

rop_descriptor:16位 光栅操作码

scale_mode:8位,缩放模式

SpiceQMask:mask; 限制copy的区域

SpiceImage *src_bitmap; 包括图像描述符SpiceImageDescriptor {

uint64_t id;

uint8_t type; //图像类型:0是位图 1是quic

uint8_t flags;

uint32_t width;

uint32_t height;}和不同类型的图像的数据结构

type字段可能的取值有:0位图,1 quic,103 from cache 当type为from cache时,从缓存中取出特定id值的图像。

Flag的可能取值有1 为SPICE_IMAGE_FLAGS_CACHE_ME,显示该图像以id为key存储到缓存中,2为SPICE_IMAGE_FLAGS_HIGH_BITS_SET 二进制表示的最高两位忽略

Spice使用图像缓存技术,将每个图像分配一个id,以哈希表的方式存储在缓存中。在服务端对使用LRU算法图像缓存进行维护。只能由服务端控制主动清除缓存,客户端没有相应的机制。

Spice图像的消息可以分为三大部分,第一部分是目的图像的位置,由一个rect和可能存在的若干clip(rect的列表)构成;第二部分是操作符,使用指定的操作符对源图像和目的图像进行相应的操作;第三部分是源图像,可能是一个图像或者调色板,或者是从缓存中获得的数据,将源图像使用操作符操作后,转成surface的图像格式,显示到目标图像。

Spice图像的来源有三种类型,一是服务端的源图像,通过网络获取或者从缓存中获取;二是调色板,即指定的颜色或指定一幅图像的某个位置的颜色;三是客户端特定位置的图像。通常使用不同的光栅操作符将三者中的某几种进行操作,最后得到最终的图像。

4.1 保持连接

使用ping和pong来保持连接。

4.2 服务器通知(错误处理)

服务端使用SPICE_MSG_NOTIFY对客户端进行消息通告,通告有三种类型,ERROR,WARN,INFO。

4.3 其他

其他的消息包括通道迁移,通道确认,关闭连接等。

服务端发送INIT消息后,客户端使用ATTACH CHANNELS消息对其进行回复。

Server name和server uuid由虚拟机的xml指定。

客户端回复attach channels对main init消息进行回复。

收到attach channels后,服务端可以使用channels_list通告客户端可用的通道。

附录:

1通道列表

enum {

SPICE_CHANNEL_MAIN = 1,

SPICE_CHANNEL_DISPLAY,

SPICE_CHANNEL_INPUTS,

SPICE_CHANNEL_CURSOR,

SPICE_CHANNEL_PLAYBACK,

SPICE_CHANNEL_RECORD,

SPICE_CHANNEL_TUNNEL,

SPICE_CHANNEL_SMARTCARD,

SPICE_CHANNEL_USBREDIR,

SPICE_CHANNEL_PORT,

SPICE_CHANNEL_WEBDAV,

SPICE_END_CHANNEL

};

2连接建立

2.1 消息格式

reds.c 2277: reds_init_client_connection 和socket有关 新建一个RedLinkInfo对象

客户端发送RedLinkMess:

typedefstruct SPICE_ATTR_PACKED SpiceLinkHeader {

uint32_t magic;

uint32_t major_version; //2

uint32_t minor_version; //2

uint32_t size;

} SpiceLinkHeader;

typedefstruct SPICE_ATTR_PACKED SpiceLinkMess {

uint32_t connection_id;

uint8_t channel_type;

uint8_t channel_id;

uint32_t num_common_caps;

uint32_t num_channel_caps;

uint32_t caps_offset;

} SpiceLinkMess;

服务端发送RedLinkReply:

typedefstruct SPICE_ATTR_PACKED SpiceLinkHeader {

uint32_t magic;

uint32_t major_version; //1

uint32_t minor_version; //0

uint32_t size;

} SpiceLinkHeader;

typedefstruct SPICE_ATTR_PACKED SpiceLinkReply {

uint32_t error; //SPICE_LINK_ERR_OK 0成功

uint8_t pub_key[SPICE_TICKET_PUBKEY_BYTES];

uint32_t num_common_caps;

uint32_t num_channel_caps;

uint32_t caps_offset;

} SpiceLinkReply;

2.2 通用属性

enum {

SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION,

SPICE_COMMON_CAP_AUTH_SPICE,

SPICE_COMMON_CAP_AUTH_SASL,

SPICE_COMMON_CAP_MINI_HEADER,

};

3 公共消息

3.1 实际使用的公共报头

typedefstruct SPICE_ATTR_PACKED SpiceMiniDataHeader {

uint16_t type;//类型

uint32_t size;//大小

} SpiceMiniDataHeader;

3.2 公共的消息类型Enums.h:422

// 服务器端

enum {

SPICE_MSG_MIGRATE = 1, //迁移

SPICE_MSG_MIGRATE_DATA,

SPICE_MSG_SET_ACK, //确认机制,客户端回复SPICE_MSGC_ACK_SYNC

SPICE_MSG_PING, //保持连接, SPICE_MSGC_PONG,

SPICE_MSG_WAIT_FOR_CHANNELS, //收到消息后,客户端等待

SPICE_MSG_DISCONNECTING, //关闭连接SPICE_MSGC_DISCONNECTING,

SPICE_MSG_NOTIFY, //通知,包括错误处理,警告,和信息

SPICE_MSG_LIST, //告知客户端通道的列表

SPICE_MSG_BASE_LAST = 100, //公共消息的类型取值为1-100

};

//客户端上的

enum {

SPICE_MSGC_ACK_SYNC = 1,

SPICE_MSGC_ACK,

SPICE_MSGC_PONG,

SPICE_MSGC_MIGRATE_FLUSH_MARK,

SPICE_MSGC_MIGRATE_DATA,

SPICE_MSGC_DISCONNECTING,

};

3.3 错误处理(ERROR CODE)Error_code.h

#define SPICEC_ERROR_CODE_SUCCESS 0

#define SPICEC_ERROR_CODE_ERROR 1

#define SPICEC_ERROR_CODE_GETHOSTBYNAME_FAILED 2

#define SPICEC_ERROR_CODE_CONNECT_FAILED 3

#define SPICEC_ERROR_CODE_SOCKET_FAILED 4

#define SPICEC_ERROR_CODE_SEND_FAILED 5

#define SPICEC_ERROR_CODE_RECV_FAILED 6

#define SPICEC_ERROR_CODE_SSL_ERROR 7

#define SPICEC_ERROR_CODE_NOT_ENOUGH_MEMORY 8

#define SPICEC_ERROR_CODE_AGENT_TIMEOUT 9

#define SPICEC_ERROR_CODE_AGENT_ERROR 10

#define SPICEC_ERROR_CODE_VERSION_MISMATCH 11

#define SPICEC_ERROR_CODE_PERMISSION_DENIED 12

#define SPICEC_ERROR_CODE_INVALID_ARG 13

#define SPICEC_ERROR_CODE_CMD_LINE_ERROR 14

3.4 SpiceNotify

typedef struct SpiceMsgNotify {

uint64_t time_stamp;

uint32_t severity;

uint32_t visibilty;

uint32_t what;

uint32_t message_len;

uint8_t message[0];

} SpiceMsgNotify;

pedef enum SpiceNotifySeverity {

SPICE_NOTIFY_SEVERITY_INFO,

SPICE_NOTIFY_SEVERITY_WARN,

SPICE_NOTIFY_SEVERITY_ERROR,

SPICE_NOTIFY_SEVERITY_ENUM_END

} SpiceNotifySeverity;

typedef enum SpiceNotifyVisibility {

SPICE_NOTIFY_VISIBILITY_LOW,

SPICE_NOTIFY_VISIBILITY_MEDIUM,

SPICE_NOTIFY_VISIBILITY_HIGH,

SPICE_NOTIFY_VISIBILITY_ENUM_END

} SpiceNotifyVisibility;

4.4 SPICE_MSG_LIST

4 Main Channel

4.1 主通道的专有属性

enum {

SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE,

SPICE_MAIN_CAP_NAME_AND_UUID,

SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS,

SPICE_MAIN_CAP_SEAMLESS_MIGRATE,

};

4.2 main通道消息类型

enum {

SPICE_MSG_MAIN_MIGRATE_BEGIN = 101,

SPICE_MSG_MAIN_MIGRATE_CANCEL,

SPICE_MSG_MAIN_INIT, //建立第一个消息

SPICE_MSG_MAIN_CHANNELS_LIST, //通道列表

SPICE_MSG_MAIN_MOUSE_MODE, //鼠标模式

SPICE_MSG_MAIN_MULTI_MEDIA_TIME, //当没有playback通道时,使用此消息进行声音和画面的同步

SPICE_MSG_MAIN_AGENT_CONNECTED,

SPICE_MSG_MAIN_AGENT_DISCONNECTED,

SPICE_MSG_MAIN_AGENT_DATA,

SPICE_MSG_MAIN_AGENT_TOKEN,

SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST,

SPICE_MSG_MAIN_MIGRATE_END,

SPICE_MSG_MAIN_NAME, // 虚拟机的名字

SPICE_MSG_MAIN_UUID, //虚拟机的uuid

SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS,

SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS,

SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK,

SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK,

SPICE_MSG_END_MAIN //结束主通道

};

enum {

SPICE_MSGC_MAIN_CLIENT_INFO = 101, //客户端信息

SPICE_MSGC_MAIN_MIGRATE_CONNECTED,

SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR,

SPICE_MSGC_MAIN_ATTACH_CHANNELS, //对init信息的回复

SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST, //

SPICE_MSGC_MAIN_AGENT_START,

SPICE_MSGC_MAIN_AGENT_DATA,

SPICE_MSGC_MAIN_AGENT_TOKEN,

SPICE_MSGC_MAIN_MIGRATE_END,

SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS,

SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS,

SPICE_MSGC_END_MAIN

};

SPICE_MSG_MAIN_CHANNELS_LIST,

typedef struct SpiceMsgChannels {

uint32_t num_of_channels;

SpiceChannelId channels[0];

} SpiceMsgChannels;

SPICE_MSGC_MAIN_CLIENT_INFO

typedef struct SpiceMsgcClientInfo {

uint64_t cache_size;

} SpiceMsgcClientInfo;

Red.c :1352

服务器通过一个回调函数接收数据包,接收到不同的数据包后,调用不同的函数进行回复。

**************reds_send_link_ack 发送RedLinkReply

5 显示通道

5.1 显示通道消息类型

服务端:

enum {

SPICE_MSG_DISPLAY_MODE = 101,

SPICE_MSG_DISPLAY_MARK,

SPICE_MSG_DISPLAY_RESET,

SPICE_MSG_DISPLAY_COPY_BITS, //将指定区域复制到目标区域

SPICE_MSG_DISPLAY_INVAL_LIST,

SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS, //清除图像缓存,spice可以缓存调色板和图像

SPICE_MSG_DISPLAY_INVAL_PALETTE, //清除调色板

SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES, //清除所有调色板

SPICE_MSG_DISPLAY_STREAM_CREATE = 122, //stream开始

SPICE_MSG_DISPLAY_STREAM_DATA, //stream数据

SPICE_MSG_DISPLAY_STREAM_CLIP,

SPICE_MSG_DISPLAY_STREAM_DESTROY,

SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL,

SPICE_MSG_DISPLAY_DRAW_FILL = 302, //指定区域绘制颜色

SPICE_MSG_DISPLAY_DRAW_OPAQUE, //将调色板和源图像结合

SPICE_MSG_DISPLAY_DRAW_COPY, //复制源图像到目标图像

SPICE_MSG_DISPLAY_DRAW_BLEND, //将源图像和目标图像混合

SPICE_MSG_DISPLAY_DRAW_BLACKNESS, //将目标区域变成黑色像素

SPICE_MSG_DISPLAY_DRAW_WHITENESS, //将目标区域变成白色像素

SPICE_MSG_DISPLAY_DRAW_INVERS, //翻转目标区域的像素

SPICE_MSG_DISPLAY_DRAW_ROP3, //将源图像、调色板和目标区域三者结合

SPICE_MSG_DISPLAY_DRAW_STROKE, //

SPICE_MSG_DISPLAY_DRAW_TEXT, //

SPICE_MSG_DISPLAY_DRAW_TRANSPARENT, //draw copy+指定颜色不显示

SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND, //使用alpha对源图像进行处理,复制到目标区域

SPICE_MSG_DISPLAY_SURFACE_CREATE, //生成一个surface

SPICE_MSG_DISPLAY_SURFACE_DESTROY, //销毁一个surface

SPICE_MSG_DISPLAY_STREAM_DATA_SIZED, //大量的stream数据

SPICE_MSG_DISPLAY_MONITORS_CONFIG, //显示屏的配置

SPICE_MSG_DISPLAY_DRAW_COMPOSITE,

SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT,

SPICE_MSG_DISPLAY_GL_SCANOUT_UNIX,

SPICE_MSG_DISPLAY_GL_DRAW,

SPICE_MSG_END_DISPLAY

};

客户端:

enum {

SPICE_MSGC_DISPLAY_INIT = 101, //指定图像缓冲区大小

SPICE_MSGC_DISPLAY_STREAM_REPORT,

SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION, //指定压缩算法

SPICE_MSGC_DISPLAY_GL_DRAW_DONE,

SPICE_MSGC_END_DISPLAY

};

5.2 DRAW_COPY

[服务端发送red_worker.c:red_marshall_image 客户端解析canvas_base.c:canvas_draw_copy]

typedef struct SpiceMsgDisplayDrawCopy {

SpiceMsgDisplayBase base;

SpiceCopy data;

} SpiceMsgDisplayDrawCopy;

typedef struct SpiceMsgDisplayBase {

uint32_t surface_id;

SpiceRect box;

SpiceClip clip;

} SpiceMsgDisplayBase;

typedef struct SpiceClip {

uint8_t type;

SpiceClipRects *rects;

} SpiceClip;

typedef struct SpiceCopy {

SpiceImage *src_bitmap;

SpiceRect src_area;

uint16_t rop_descriptor;

uint8_t scale_mode;

SpiceQMask mask;

} SpiceCopy, SpiceBlend;

copy_bits:

把源区域的图像拷贝到目标区域,两个区域可能会重叠。指定矩形区域的左上角,矩形区域和目标区域的大小一样。

Draw_fill:

将固定的颜色填充到指定区域,颜色的格式和suface的格式一样,rgba各一个字节,16进制。

Draw_opaque:

将源图和调色板结合,渲染到目标区域

SPICE_ROPD_INVERS_SRC = (1 << 0), //源图像取反SPICE_ROPD_INVERS_BRUSH = (1 << 1), //调色刷取反SPICE_ROPD_INVERS_DEST = (1 << 2), //目标区域渲染前取反SPICE_ROPD_OP_PUT = (1 << 3), //复制SPICE_ROPD_OP_OR = (1 << 4), //orSPICE_ROPD_OP_AND = (1 << 5), //andSPICE_ROPD_OP_XOR = (1 << 6), //xorSPICE_ROPD_OP_BLACKNESS = (1 << 7), SPICE_ROPD_OP_WHITENESS = (1 << 8),SPICE_ROPD_OP_INVERS = (1 << 9), //目标像素取反SPICE_ROPD_INVERS_RES = (1 << 10), //结果取反