Spice Guest Agent是运行在Guest OS内部的增强spice协议性能的重要组件。主要实现了:鼠标客户端模式,自动调节分辨率,剪切板复制,文件拖拽等重要功能。如果没有dagent程序,那么spice协议体检是无法接受的。
vdgent程序
- 在Linux上使用串口:
/dev/virtio-ports/com.redhat.spice.0
- 在Windows上使用串口:
\\\\.\\Global\\com.redhat.spice.0
- qemu启动参数:
device virtio-serial-pci,id=virtio-serial0,max_ports=16,bus=pci.0,addr=0x5 -chardev spicevmc,name=vdagent,id=vdagent -device virtserialport,nr=1,bus=virtio-serial0.0,chardev=vdagent,name=com.redhat.spice.0
协议头
1.VDIChunkHeader
vdagent 通过virtio串行端口与spice 服务端连接,可以发送数据到spice服务器和spice客户端,也可以接收它们发过来的数据。VDIChunkHeader作为消息头。
typedef struct SPICE_ATTR_PACKED VDIChunkHeader {
uint32_t port; //vdagent接收消息时:port是消息来源,dagent发送消息时:port是接收方
uint32_t size; //size是发送或接收到的数据的大小,包括可变数据部分的大小, size=sizeof(VDAgentMessage)+variable_data_len
} VDIChunkHeader;
元素port的值:
enum {
VDP_CLIENT_PORT = 1,
VDP_SERVER_PORT,
};
当要发送的消息是对已接收消息的应答消息,port的值应与已接收到的消息中的port值一致。
当spice server收到一条消息时,会判断VDIChunkHeader中的port数据,来决定是将数据直接发给客户端还是自己进行处理(如果是无效的value则丢弃)。目前还没有来自Agent的消息是针对spice server的,因此所有port值为VDP_SERVER_PORT的消息都会被服务器默默的丢弃。
2.VDAgentMessage
Agent发送或接收到的消息都被封装到一个叫VDAgentMessage的结构体中:
typedef struct SPICE_ATTR_PACKED VDAgentMessage {
uint32_t protocol; //固定值VD_AGENT_PROTOCOL
uint32_t type; //消息类型,在一个enum中
uint64_t opaque; //opaque是消息类型的占位符,只需要将单个整数作为消息数据传递,对于具有更多数据的消息类型,opaque值始终设置为0.
uint32_t size; // 可变长度数据的大小,值为:sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + variable_data_len.
uint8_t data[0]; //可变长度数据的起始地址,可变长度数据的内容取决于数据类型,对于大多数消息,它是一种数据类型特定的结构体,如VDAgentMouseSate。
} VDAgentMessage;
消息类型:即VDAgentMessage的type字段,每个元素代表一条协议
enum {
VD_AGENT_MOUSE_STATE = 1,
VD_AGENT_MONITORS_CONFIG,
VD_AGENT_REPLY,
VD_AGENT_CLIPBOARD,
VD_AGENT_DISPLAY_CONFIG,
VD_AGENT_ANNOUNCE_CAPABILITIES,
VD_AGENT_CLIPBOARD_GRAB,
VD_AGENT_CLIPBOARD_REQUEST,
VD_AGENT_CLIPBOARD_RELEASE,
VD_AGENT_FILE_XFER_START,
VD_AGENT_FILE_XFER_STATUS,
VD_AGENT_FILE_XFER_DATA,
VD_AGENT_CLIENT_DISCONNECTED,
VD_AGENT_MAX_CLIPBOARD,
VD_AGENT_END_MESSAGE,
};
协议详细信息
1.鼠标模式(VD_AGENT_MOUSE_STATE)
spice server ---> vdagent
typedef struct SPICE_ATTR_PACKED VDAgentMouseState {
uint32_t x;
uint32_t y;
uint32_t buttons;
uint8_t display_id;
} VDAgentMouseState;
spice支持两种鼠标模式,server和client。
在server模式下,QEMU ps/2鼠标仿真用于向客户机(Guest)发送鼠标状态。鼠标进入客户端窗口时候,俘获鼠标,客户端将鼠标移动增量坐标发送到客户机(Guest)。效果极差。
在client模式下,鼠标在显示器中的坐标位置作为绝对值发送给客户机。这需要使用usb表仿真,或将它们发送给vdagent,vdagent将通知客户机操作系统鼠标位置(和按钮单击)。
当鼠标处于client模式时,spice server端通过agent协议VD_AGENT_MOUSE_STATE,发送VDAgentMouseState消息给vdagent,由vagent调整guest os的鼠标位置。
注意:这些消息是由spice server发送的,不是由spice 客户端发送的,因为spice server执行所有的鼠标处理(如在vdagent连接/断开连接时切换client模式或server模式)。
2.显示器配置(VD_AGENT_MONITORS_CONFIG)
spice client ---> vdagent
typedef struct SPICE_ATTR_PACKED VDAgentMonitorsConfig {
uint32_t num_of_monitors;
uint32_t flags;
VDAgentMonConfig monitors[0];
} VDAgentMonitorsConfig
紧跟着该消息后面的是num_of_moniters个以下结构体:
typedef struct SPICE_ATTR_PACKED VDAgentMonConfig {
uint32_t height;
uint32_t width;
uint32_t depth;
int32_t x;
int32_t y;
} VDAgentMonConfig;
当客户端以全屏自动配置模式运行时,客户端会将此消息发送给vdagent。此消息包含连接到客户端计算机的显示器的信息。收到此消息后,vdagent应重新配置来宾中qxl vga设备的输出,以尽可能匹配消息中的输出。
当vdagent完成了配置,agent应该返回VD_AGENT_REPLY消息,该消息中的type值设置为VD_AGENT_MONITORS_CONFIG,error值设置为VD_AGENT_SUCCESS或者VD_AGENT_ERROR,以指示配置是成功或是错误。
3.消息应答(VD_AGENT_REPLY)
vdagent ---> spice client
typedef struct SPICE_ATTR_PACKED VDAgentReply {
uint32_t type;
uint32_t error;
} VDAgentReply;
其中:error值为:
enum {
VD_AGENT_SUCCESS = 1,
VD_AGENT_ERROR,
};
该消息由vdagent发送给client,表示vdagent已经完成了对VD_AGENT_MONITORS_CONFIG 或者 VD_AGENT_DISPLAY_CONFIG消息的处理,以及处理成功还是失败。
4.通知自己支持的功能(VD_AGENT_ANNOUNCE_CAPABILITIES)
spice client ---> vdagent vdagent ---> spice client 这条协议由spice client和vdagent通知自己支持的功能。此消息的目的是允许不同版本的客户端和vdagent能一起工作。
typedef struct SPICE_ATTR_PACKED VDAgentAnnounceCapabilities {
uint32_t request;
uint32_t caps[0];
} VDAgentAnnounceCapabilities;
request字段是一个布尔值,指示了信息的接收者是否需要回复一个 VD_AGENT_ANNOUNCE_CAPABILITIES 的信息,因为发送者可能也想知道接收者的capabilities。在最开始发起capabilities交换时这个应该设置为true,当发送一个宣告capabilities作为对所接收到的信息的应答时,该值设置为false。
caps成员是一个可变长度数组的首地址。该数组的长度可以使用VDAgentMessage结构体中的size成员上的VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE 宏来确定。不同capabilities 的索引位于定义在VD_AGENT_CAP 常量的枚举中。并且有VD_AGENT_HAS_CAPABILITY和VD_AGENT_SET_CAPABILITY宏来测试/设置数组中的capability 位。
5. 显示效果优化(VD_AGENT_DISPLAY_CONFIG)
spice client ---> vdagent
typedef struct SPICE_ATTR_PACKED VDAgentDisplayConfig {
uint32_t flags;
uint32_t depth;
} VDAgentDisplayConfig;
enum {
VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_WALLPAPER = (1 << 0),
VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_FONT_SMOOTH = (1 << 1),
VD_AGENT_DISPLAY_CONFIG_FLAG_DISABLE_ANIMATION = (1 << 2),
VD_AGENT_DISPLAY_CONFIG_FLAG_SET_COLOR_DEPTH = (1 << 3),
};
spice客户端发送给vdagent,来通知vdagent一些特殊性能的相关设置。客户端可以要求vdagent禁用客户机系统的的许多功能,如字体反混叠等,以提高性能。vdagent可以在这些方面做一些努力,尤其是因为大多数设置都是以windows为中心的。应该使用VD_AGENT_REPLY 返回成功状态,除非出现问题。
6.剪切板相关协议
客户机上的agent和客户端起着对称的作用:它们都可以声明拥有权(GRAB),释放拥有权(RELEASE),请求剪贴板数据(REQUEST)以及发送剪贴板数据。当剪贴板清空时,grab信息必须被释放(release)掉。
另一端在grab处于活动状态时可以请求数据,之后会收到对方回复的带有剪贴板信息的CLIPBOARD消息。
剪贴板数据类型如下:
enum {
VD_AGENT_CLIPBOARD_NONE = 0,
VD_AGENT_CLIPBOARD_UTF8_TEXT,
VD_AGENT_CLIPBOARD_IMAGE_PNG, /* All clients with image support should support this one */
VD_AGENT_CLIPBOARD_IMAGE_BMP, /* optional */
VD_AGENT_CLIPBOARD_IMAGE_TIFF, /* optional */
VD_AGENT_CLIPBOARD_IMAGE_JPG, /* optional */
};
如果双方都实现了VD_AGENT_CAP_CLIPBOARD_SELECTION的功能,clipboard信息前面都要带有一个uint8_t的值,指示要操作的clipboard selection。
VD_AGENT_CLIPBOARD
struct VDAgentClipboard {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
uint8_t selection;
uint8_t __reserved[3];
endif
uint32_t type; //type值是剪贴板数据类型,即前面剪贴板数据枚举类型中的一个。
uint8_t data[0]; //可变长度数据的起始地址
};
VD_AGENT_CLIPBOARD 用于发送剪贴板数据。除非接收到VD_AGENT_CLIPBOARD_REQUEST 的数据请求,否则这个数据不会被发送,以避免浪费宽带。被传送的剪贴板数据通常来说都挺大的,在这种情况下,可以预期到,要传送的数据是要分割成多个VD_AGENT_MESSAGE发送的。
VD_AGENT_CLIPBOARD_REQUEST
struct VDAgentClipboardRequest {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
uint8_t selection;
uint8_t __reserved[3];
endif
uint32_t type; //剪贴板数据类型,即前面剪贴板数据枚举类型中的一个。
};
请求具有指定类型的剪贴板数据。
VD_AGENT_CLIPBOARD_GRAB
struct VDAgentClipboardGrab {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
uint8_t selection;
uint8_t __reserved[3];
endif
uint32_t types[0];
};
抓取剪贴板数据。任何抓取类型的数据请求都应该成功。
VD_AGENT_CLIPBOARD_RELEASE
struct VDAgentClipboardRelease {
if VD_AGENT_CAP_CLIPBOARD_SELECTION capability
uint8_t selection;
uint8_t __reserved[3];
endif
};
释放剪贴板。注意:如果一条GRAB信息已被发送并且在当前处于活动状态,然后又从对方那里接收到连续的GRAB信息,这时不应该发送RELEASE信息给对方来取消上一条发过去的grab。因为那条处于活动状态的grab已经被对方暗中释放掉了。如果给对方发送额外的RELEASE信息,只会使对方感到困惑。
VD_AGENT_CAP_CLIPBOARD_SELECTION
当客户端和服务器都具有selection capability,VDAgentClipboard信息必须在前面加上一个uint8_t,该uint8_t指示哪个 clipboard selection 可以操作+3个字节填充以供未来功能或扩展使用。
根据X11 / Gtk方案定义了几个剪贴板选择:
- (1)VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD: 默认剪贴板,由大多数操作系统实现,以处理显式的复制/粘贴操作。
- (2)VD_AGENT_CLIPBOARD_SELECTION_PRIMARY: PRIMARY剪贴板,用于鼠标选择。
- (3)VD_AGENT_CLIPBOARD_SELECTION_SECONDARY: the SECONDARY clipboard.
以spice client ---> vdagent为例:
- spice client发送VD_AGENT_CLIPBOARD_GRAB消息,告诉spice client数据已经更改。
- vdagent受到VD_AGENT_CLIPBOARD_GRAB消息,将剪切板owner设为spice client,并发送VD_AGENT_CLIPBOARD_REQUEST消息给spice client
- spice client收到D_AGENT_CLIPBOARD_REQUEST消息,发送回包,带:VDagentClipboard结构体数据
- vdagent接收到DagentClipboard结构体,获取剪切板数据,更新guest os的剪切板
7.文件传输相关协议
spice协议支持把文件从spice client拖拽到虚拟机内部。但是不支持反向操作。
VD_AGENT_FILE_XFER_START
typedef struct SPICE_ATTR_PACKED VDAgentFileXferStartMessage {
uint32_t id;
uint8_t data[0];
} VDAgentFileXferStartMessage;
VD_AGENT_FILE_XFER_STATUS
typedef struct SPICE_ATTR_PACKED VDAgentFileXferStatusMessage {
uint32_t id;
uint32_t result;
/* Used to send additional data for detailed error messages
* to clients with VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS capability.
* Type of data varies with the result:
* result : data type (NULL if no data)
* VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE : uint64_t
* VD_AGENT_FILE_XFER_STATUS_SESSION_LOCKED : NULL
* VD_AGENT_FILE_XFER_STATUS_VDAGENT_NOT_CONNECTED : NULL
* VD_AGENT_FILE_XFER_STATUS_DISABLED : NULL
*/
uint8_t data[0];
} VDAgentFileXferStatusMessage;
VD_AGENT_FILE_XFER_DATA
typedef struct SPICE_ATTR_PACKED VDAgentFileXferDataMessage {
uint32_t id;
uint64_t size;
uint8_t data[0];
} VDAgentFileXferDataMessage;