Spice VDAgentMessage 协议及使用
I.概要
Spice 包含三块:绑定在qemu/kvm上的Server(模块), 运行在(远程)主机上的Client(程序),和运行在Guest(虚拟机)上Agent(程序),(虚拟机运行在qemuspice上)。Server将运行在qemuspice上的虚拟机的数据通过channel(socket)传给client 显示输出,并和后者交互;agent 会辅助server/client在虚机上执行一些任务。
Win32中,Spice Agent是服务程序,由vdservice.exe和vdagent.exe组成,前者负责与系统和VDI端口通信,后者负责具体任务。二者之间建立pipe通信。
II.VDAgentMessage结构和操作
1.VDAgentMessage结构示例
这三方关于agent任务的所有数据通信,都基于struct VDAgentMessage
typedef struct SPICE_ATTR_PACKED VDAgentMessage {
uint32_t protocol; //协议名长为32字节的unsigned int
uint32_t type; //类别:剪贴板,鼠标事件...
uint64_t opaque; //掩码:消息发往何处
uint32_t size; //大小=data的长度+某子类型长度
uint8_t data[0]; //附加数据,实际是uint8_t*,所有msg本质上都存在uint8_t[]内。
} VDAgentMessage;
VDAgentMessage存于长度可变的uint8_t[]内,可对.type和.data处理,生成各种子类型。
具体为例,spice实现了agent所在的虚机和client所在的主机共享剪贴板,剪贴板消息的数据结构是
struct VDAgentMessage clip_msg =
new uint8_t[_out_msg_size=sizeof(VDAgentMessage)+sizeof(VDAgentClipboard)+clip_data_len] ={
.protocol = VD_AGENT_PROTOCOL;
.type = VD_AGENT_CLIPBOARD;
.opaque= ...方向
.size= sizeof(VDAgentClipboard)+clip_data_len;
.data= new uin8_t[.size];
};
此时clip_msg.data 即为(VDAgentClipboard*)clip_msg.data:
struct VDAgentClipboard//即把clip_msg.data[]内的数据进行细分当结构体看待:
{
.uint32_t type, //剪贴数据类型
.uint8_t* data = new uint8_t[clip_data_len],//这才是剪贴的数据clip_data
};
2.VDAgentMessage处理过程(以VDAgentClipboard 为例)
1)从虚机复制单词“Massacre”,被agent捕获,同时向client发出请求锁定client所在主机的剪贴板
Agent(VDAgentMessage clip_grab_msg)>Server>Client
2)此时若在在client主机内执行粘贴则被client 捕获,向agent 发出数据请求
Client(VDAgentMessage clip_data_request_msg)>Server>Agent
3)受到请求后,agent将从虚机剪贴板上取出的“Massacre”加工成VDAgentMessage包装好发出
Agent(VDAgentMessage clip_data_msg)>Server>Client
4)client 将其解包取出“Massacre”,加入主机的剪贴板内>“Massacre”贴到client所在的主机上。其中Agent内从vdagent发出的VDAgentMessage _out_msg 先包装成VDPipeMessage _pipe_msg ={
u32t .type = VD_AGENT_COMMAND;//亦即发往VDIPort而非止步于vdservice
u32t .opaque=VDP_CLIENT_PORT;//最终发往client
u32t .size= _out_msg_size
u8t* .data=new uint8_t[.size] = (VDAgentMessage*) _out_msg
}
_pipe_msg通过pipe发往vdservice, 然后if(_pipe_msg.type ==VD_AGENT_COMMAND),则vdservice先向vdiport write_ring发出VDAChunkHeader chunk =
{
u32t .port= pipe_msg.opaque;
u32t .size= pipe_msg.size= _out_msg_size;
}
再发出pipe_msg.data =_out_msg.
3.具体过程(函数路线)(待细化)
1)Agent:
从2.3)开始
vdagent.cpp: VDAgent::handle_clipboard_request()
{生成.data.data=clip_data的VDAgentMessage _out_msg;}
>vdagent.cpp: VDAgent::write_clipboard()
{生成.data=_out_msg 的VDPipeMessage pipe_msg;}
>vdagent.cpp: VDAgent::write_completion(){将pipe_msg写入pipe}
vdservice读取pipe(略),
vservice.cpp: VDService::handle_pipe_data()
{经判断,将取得的pipe_msg发往VDI设备输入环:
先发出VDAChunkHeader chunk,再发pipe_msg.data=_out_msg}
vdservice 写入vdiport(略)
2)Server:
reds.c: read_from_vdi_port()
{
从vdiport中读出信息,spice service 的所有管理信息都在
struct RedsState* reds =
{
int listen_socket;
...
VDIPortState agent_state;
InputsState *inputs_state;
IncomingHandler in_handler;
RedsOutgoingData outgoing;
Channel *channels; ...
}; 内,其中
struct VDIPortState
{
...
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;
};
struct VDIReadBuf
{
RingItem link;
int len;
uint8_t data[SPICE_AGENT_MAX_DATA_SIZE];
};
VDIPortState* state= (VDIPortState*) &reds>agent_state;
VDIReadBuf* dispatch_buf;
在state的管理及辅助下(略),Agent发来的chunk信息读入state内,pipe_msg.data =_out_msg保存于dispatch_buf>data内。
}
>reds.c: dispatch_vdi_port_data(state>vdi_chunk_header.port,dispatch_buf)
{
用dispatch_buf信息生成RedsOutItem item,再reds_push_pipe_item(item),
通过channel发往client.
}
3.)Client (略)
III.新的VDAgentMessage子类型添加实例
1.添加类型
在vdagent.h中为VDAgentMessage.type添加新的种类:enum{
VD_AGENT_MOUSE_STATE=1,
... VD_AGENT_ADMIN,//虚机管理消息
...};
新的结构体(将位于msg.data内):
typedef struct SPICE_ATTR_PACKED VDAgentAdmin
{
.uint32_t type, //管理消息类型
.uint8_t* data,//附带信息
}VDAgentAdmin;
enum{//管理消息类型
VD_AGENT_ADMIN_SHUTDOWN=1;
VD_AGENT_ADMIN_REBOOT,...
};
2.使用过程(server 发出shutdown命令给agent):
1)
即server执行
vdagent_guest_admin(SHUTDOWN);
void vdagent_guest_admin(int vd_admin)
{
RingItem *ring_item;
VDAgentExtBuf *buf;
if (!reds>agent_state.num_client_tokens) {
red_printf("token violation");
return;
}
reds>agent_state.num_client_tokens;
if (!vdagent) {
add_token();
return;
}
if (!reds>agent_state.client_agent_started) {
red_printf("SPICE_MSGC_MAIN_AGENT_DATA race");
add_token();
return;
}
if (!(ring_item = ring_get_head(&reds>agent_state.external_bufs))) {
red_printf("no agent free bufs");
return;
}
VDAgentMessage* message;
uint32_t size;
vdagent_admin_msg(vd_admin,&message,&size); //server 生成msg,定义在下面 ring_remove(ring_item);
buf = (VDAgentExtBuf *)ring_item;
buf>base.now = (uint8_t *)&buf>base.chunk_header.port;
buf>base.write_len = size + sizeof(VDIChunkHeader);
buf>base.chunk_header.size = size;
memcpy(buf>buf, message, size);
ring_add(&reds>agent_state.write_queue, ring_item);
write_to_vdi_port();//将msg写入vdiport
free(message);
red_printf("Guest is being shutdown,for disaster is looming...");
}
void vdagent_admin_msg(int vd_admin,VDAgentMessage** ppmsg,uint32_t* msg_size){
uint8_t* data;
uint32_t size;
switch(vd_admin)
{
case VD_AGENT_ADMIN_SHUTDOWN:
size=10;
data=(uint8_t*)malloc(size);
snprintf(data,size,"%s",(uint8_t*)"Apocalypse");//目前无用
break;
default:
break;
}
*msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentAdmin) + size;
(*ppmsg) = (VDAgentMessage*) (uint8_t*)malloc(*msg_size);
(*ppmsg)>protocol = VD_AGENT_PROTOCOL;
(*ppmsg)>type = VD_AGENT_ADMIN;//虚机管理消息
(*ppmsg)>opaque = 0;//方向:agent
(*ppmsg)>size = sizeof(VDAgentAdmin) + size;
VDAgentAdmin* admin = (VDAgentAdmin*)((*ppmsg)>data);
admin>type = vd_admin;//=SHUTDOWN
memcpy(admin>data, data, size);//无用
free(data);
}
2. agent
vdservice会一如往常,从vdiport读出包含这个VDAgentMessage的数据包,加工成pipe_msg发往pipe>vdagent,后者会从pipe内读出pipe_msg:
VDAgent::read_completion()
{
VDPipeMessage pipe_msg;
...
do_admin_jobs((VDAgentMessage*)pipe_msg>data);//监视有无虚机管理消息
...
}
void VDAgent::do_admin_jobs(VDAgentMessage* msg)
{
if(msg>type == VD_AGENT_ADMIN)
{
FILE* f=fopen("C:\\aho.txt","w");
fprintf(f,"%s:I'm searching demons...\n",__FUNCTION__);
VDAgentAdmin* admin = (VDAgentAdmin*)msg>data;
if(admin>type == VD_AGENT_ADMIN_SHUTDOWN)
{
system("shutdown s t 0");//关闭虚机
fprintf(f,"%s:Hoorrrorrr! Apocalypse has come!!!.Shutdownnow...\n",
__FUNCTION__);
fclose(f);
}
}
}