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/

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐