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;