MongoDB 是由 MongoDB Inc 开发的 NoSQL 数据库,它是无模式的。它是使用 c++ 和 javascript 设计和创建的,允许更高的连接性。
MongoDB 是一个 NoSQL Server,其中数据存储在 BSON(二进制 JSON)文档中,每个文档本质上都建立在键值对结构上。由于 MongoDB 很容易存储无模式数据,因此使其适合捕获结构未知的数据。
MongoDB Wire Protocol
MongoDB Wire Protocol 是一个简单的基于套接字的请求-响应式协议。 客户端通过常规 TCP/IP 套接字与数据库服务器通信。通常,Mongo 使用 TCP 作为其传输协议。 Mongo 流量的著名 TCP 端口是 27017。
MongoDB 将数据记录存储为 BSON 文档。 BSON 是 JSON 文档的二进制表示,尽管它包含比 JSON 更多的数据类型。
MongoDB 文档由字段-值对组成。
Mongo 消息类型和格式
MongoDB 对客户端请求和数据库回复都使用 OP_MSG 操作码。
#define OP_REPLY 1
#define OP_MESSAGE 1000
#define OP_UPDATE 2001
#define OP_INSERT 2002
#define OP_RESERVED 2003
#define OP_QUERY 2004
#define OP_GET_MORE 2005
#define OP_DELETE 2006
#define OP_KILL_CURSORS 2007
#define OP_COMMAND 2010
#define OP_COMMANDREPLY 2011
#define OP_COMPRESSED 2012
#define OP_MSG 2013
{ OP_REPLY, "Reply" },
{ OP_MESSAGE, "Message" },
{ OP_UPDATE, "Update document" },
{ OP_INSERT, "Insert document" },
{ OP_RESERVED,"Reserved" },
{ OP_QUERY, "Query" },
{ OP_GET_MORE, "Get More" },
{ OP_DELETE, "Delete document" },
{ OP_KILL_CURSORS, "Kill Cursors" },
{ OP_COMMAND, "Command Request" },
{ OP_COMMANDREPLY, "Command Reply" },
{ OP_COMPRESSED, "Compressed Data" },
{ OP_MSG, "Extensible Message Format" },
Mongo 标准消息头
通常,每条消息都由一个标准消息头和请求特定的数据组成。
消息长度:消息的总大小(以字节为单位)。 这个总数包括保存消息长度的 4 个字节。
请求ID:唯一标识消息的客户端或数据库生成的标识符。
响应:从客户端的消息中获取的 requestID。
操作码:消息类型。
Query 消息
OP_QUERY消息用于在数据库中查询集合中的文档。
messageLength: 报文总长度,使用 4 个字节表示。
requestID : 由客户端或服务器生成的报文唯一标识。 例如客户端请求 (OP_QUERY 和 OP_GET_MORE), 将返回 OP_REPLY 报文给 responseTo 字段表示的请求. 客户端可以使用 requestID 和 responseTo 字段关联查询响应到原始请求上。
responseTo : 服务端响应使用此字段关联到 requestID 对应的查询请求上。
opCode: 报文类型。
fullCollectionName: 完整的集合名称;即命名空间。
number To Skip:要跳过的文件数
number To Return:表示第一个 OP_REPLY 响应中的文档返回数
Reply 消息
该OP_REPLY消息由数据库发送,以响应OP_QUERY或OP_GET_MORE消息。
cursorID :会在 OP_REPLY 中设置
number Return:返回的文档数量
document: 一个或多个要插入到集合中的文档。如果有多个,它们会依次写入到插槽中。
Insert document 消息
OP_INSERT消息用于将一个或多个文档插入到集合中。
document: 一个或多个要插入到集合中的文档。如果有多个,它们会依次写入到插槽中。
fullCollectionName:完整的集合名称;即命名空间。完整的集合名称是数据库名称与集合名称.的拼接,使用一个for concatenation。例如,对于数据库foo和集合bar,完整的集合名称是foo.bar。
Update document 消息
OP_UPDATE消息用于更新集合中的文档。
ZERO: 0 - 保留给以后使用
selector:文档查询条件,BSON文档,指定要更新的文档的选择查询。
update: 指定要执行的更新
Delete document 消息
OP_DELETE消息用于从集合中删除一个或多个文档。
ZERO:整数值为0.保留以备将来使用。
selector:代表用于选择要删除的文档的查询的BSON文档。选择器将包含一个或多个元素,所有这些元素都必须与要从集合中移除的文档匹配。
Get More 消息
OP_GET_MORE消息用于在数据库中查询集合中的文档。
ZERO: 0 - 保留给以后使用
number To Return: 返回的文档数量,如果numberToReturn是0,数据库将使用默认的返回大小。
curso rID :会在 OP_REPLY 中设置
数据库将用OP_REPLY消息响应OP_GET_MORE消息。
Kill Cursors 消息
OP_KILL_CURSORS消息用于关闭数据库中的活动光标。这是确保在查询结束时回收数据库资源所必需的。
ZERO:整数值为0.保留以备将来使用。
numberOfCursorIDs:消息中的光标ID的数量。
cursorIDs:游标ID的“数组”被关闭。如果有多个,它们会依次写入到插槽中。
如果游标被读取直到耗尽(读取直到OP_QUERY或OP_GET_MORE为游标ID返回0),则不需要终止游标。
MongoDB数据库协议解析及C/C++代码实现
...
static int dissect_opcode_types(u_char *mongo_data, unsigned int offset, unsigned int opcode, int message_length)
{
if(opcode == 1)
{
printf("Response :");
}
else
{
printf("Request :");
}
printf("%s\n", val_to_str_const(opcode, opcode_vals, "Unknown"));
switch(opcode)
{
case OP_REPLY:
offset = dissect_mongo_reply(mongo_data, offset, message_length);
break;
case OP_MESSAGE:
offset = dissect_mongo_msg(mongo_data, offset, message_length);
break;
case OP_UPDATE:
offset = dissect_mongo_update(mongo_data, offset, message_length);
break;
case OP_INSERT:
offset = dissect_mongo_insert(mongo_data, offset, message_length);
break;
case OP_QUERY:
offset = dissect_mongo_query(mongo_data, offset, message_length);
break;
case OP_GET_MORE:
offset = dissect_mongo_getmore(mongo_data, offset, message_length);
break;
case OP_DELETE:
offset = dissect_mongo_delete(mongo_data, offset, message_length);
break;
case OP_KILL_CURSORS:
offset = dissect_mongo_kill_cursors(mongo_data, offset, message_length);
break;
case OP_COMMAND:
offset = dissect_mongo_op_command(mongo_data, offset, message_length);
break;
case OP_COMMANDREPLY:
offset = dissect_mongo_op_commandreply(mongo_data, offset, message_length);
break;
case OP_COMPRESSED:
offset = dissect_mongo_op_compressed(mongo_data, offset, message_length);
break;
case OP_MSG:
offset = dissect_mongo_op_msg(mongo_data, offset, message_length);
break;
default:
/* No default Action */
break;
}
return offset;
}
....
int main(int argc, char* argv[])
{
char errbuf[1024];
pcap_t *desc = 0;
char *filename = argv[1];
if (argc != 2)
{
printf("usage: ./dissect_mongo [pcap file]\n");
return -1;
}
printf("ProcessFile: process file: %s\n", filename);
if ((desc = pcap_open_offline(filename, errbuf)) == NULL)
{
printf("pcap_open_offline: %s error!\n", filename);
return -1;
}
pcap_loop(desc, pkt_number, (pcap_handler)ace_pcap_hand, NULL);
pcap_close(desc);
return 0;
}
运行结果:
总结
MongoDB Wire Protocol是一个简单的基于socket,请求/响应方式的协议,客户端使用常规的TCP/IP套接字(socket)进行通信,服务端默认监听端口是 27017。
参考:https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/