在第1部分和第2部分中,我们查看了MongoDB有线协议消息的消息头。这次是解析消息内容的时候了。但是,我们实际上不会解码MongoDB返回的文档,因为这不属于本教程的范围。
解码OP_QUERY消息
OP_QUERY消息用于在数据库中查询集合中的文档。此消息的格式为:
各种领域的含义可以在specification中看到。在消息头中我们只需要处理int32s,但现在我们也有一个字符串。我们可以从解析flags
字段开始:
mongodb_protocol = Proto("MongoDB", "MongoDB Protocol")
-- Header fields
message_length = ProtoField.int32("mongodb.message_length", "messageLength", base.DEC)
request_id = ProtoField.int32("mongodb.requestid" , "requestID" , base.DEC)
response_to = ProtoField.int32("mongodb.responseto" , "responseTo" , base.DEC)
opcode = ProtoField.int32("mongodb.opcode" , "opCode" , base.DEC)
-- Payload fields
flags = ProtoField.int32("mongodb.flags" , "flags" , base.DEC)
mongodb_protocol.fields = {
message_length, request_id, response_to, opcode, -- Header
flags -- OP_QUERY
}
function mongodb_protocol.dissector(buffer, pinfo, tree)
length = buffer:len()
if length == 0 then return end
pinfo.cols.protocol = mongodb_protocol.name
local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")
-- Header
subtree:add_le(message_length, buffer(0,4))
subtree:add_le(request_id, buffer(4,4))
subtree:add_le(response_to, buffer(8,4))
local opcode_number = buffer(12,4):le_uint()
local opcode_name = get_opcode_name(opcode_number)
subtree:add_le(opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")
-- Payload
if opcode_name == "OP_QUERY" then
local flags = buffer(16,4):le_uint()
subtree:add_le(flags, buffer(16,4))
end
end
function get_opcode_name(opcode)
...
end
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)
为了使消息的头部和实际有效负载之间的区别更加清晰,我们将使用注释来显示不同部分的起始位置。因为不同的操作码具有不同的结构,我们必须使用if语句检查我们正在解析的消息类型。我们只是在上面的代码中解析OP_QUERY
消息。
该脚本现在开始变得很大,所以我将开始缩短我们之前已经看过的内容...
因此,现在,flags
字段显示在子树中,用于OP_QUERY
消息。与opcode
类似,如果可以在值旁边的括号中描述标志值,那将是很好的。值的描述可在规范中找到。与操作码描述一样,我们创建一个查找函数来获取flag
描述:
function get_flag_description(flags)
local flags_description = "Unknown"
if flags == 0 then flags_description = "Reserved"
elseif flags == 1 then flags_description = "TailableCursor"
elseif flags == 2 then flags_description = "SlaveOk.Allow"
elseif flags == 3 then flags_description = "OplogReplay"
elseif flags == 4 then flags_description = "NoCursorTimeout"
elseif flags == 5 then flags_description = "AwaitData"
elseif flags == 6 then flags_description = "Exhaust"
elseif flags == 7 then flags_description = "Partial"
elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end
return flags_description
end
然后更改我们将字段添加到子树的方式:
if opcode_name == "OP_QUERY" then
local flags = buffer(16,4):le_uint()
local flags_description = get_flag_description(flags)
subtree:add_le(flags, buffer(16,4)):append_text(" (" .. flags_description .. ")")
end
对于带有OP_QUERY
操作码的消息,MongoDB子树将如下所示:
flag
字段不会出现在其他消息中,因为它们永远不会进入OP_QUERY
if
块。下一个字段与前一个字段略有不同:我们现在必须解析除了int32之外的其他字段。在这里,它是一个字符串。字符串与其他类型不同,因为它没有固定长度。所以我们必须遍历缓冲区中的字节,直到我们到达字符串的末尾。我们如何确定字符串的结尾取决于它是什么类型的字符串。在这种情况下,它是一个cstring,这意味着字符串由NUL(字节00)终止。
-- Loop over string
local string_length
for i = 20, length - 1, 1 do
if (buffer(i,1):le_uint() == 0) then
string_length = i - 20
break
end
end
subtree:add_le(full_coll_name, buffer(20,string_length))
我们从字符串的开头(字节20)遍历到整个消息的末尾。然后我们使用buffer(i,1):le_uint()
一次读取一个字节,并检查它是否是NUL字节,表示字符串的结尾。如果是,我们将字符串的长度存储在string_length
中并打破循环。
然后我们可以将该字段添加到子树中。我们还必须通过将此行添加到脚本顶部来创建该字段:
full_coll_name = ProtoField.string("mongodb.full_coll_name", "fullCollectionName", base.ASCII)
我们可以看到我们这次使用ProtoField的string
函数而不是int32
。我们还想用ASCII而不是十进制来表示字符串,所以我们必须使用base.ASCII
。该字段还必须添加到fields
表中:
mongodb_protocol.fields = {
message_length, request_id, response_to, opcode, -- Header
flags, full_coll_name -- OP_QUERY
}
我们现在在数据包详细信息窗格中有了集合名称:
其余的字段很简单。我不会详细解释它们,而是显示最终代码。名为
query
的字段包含BSON文档,但如前所述,解码它们超出了本文的范围。我将ProtoField.none
用于该字段,这是一种可用于非结构化数据的类型。添加OP_QUERY
的脚本是:
mongodb_protocol = Proto("MongoDB", "MongoDB Protocol")
-- Header fields
message_length = ProtoField.int32 ("mongodb.message_length" , "messageLength" , base.DEC)
request_id = ProtoField.int32 ("mongodb.requestid" , "requestID" , base.DEC)
response_to = ProtoField.int32 ("mongodb.responseto" , "responseTo" , base.DEC)
opcode = ProtoField.int32 ("mongodb.opcode" , "opCode" , base.DEC)
-- Payload fields
flags = ProtoField.int32 ("mongodb.flags" , "flags" , base.DEC)
full_coll_name = ProtoField.string("mongodb.full_coll_name" , "fullCollectionName", base.ASCII)
number_to_skip = ProtoField.int32 ("mongodb.number_to_skip" , "numberToSkip" , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn" , base.DEC)
query = ProtoField.none ("mongodb.query" , "query" , base.HEX)
mongodb_protocol.fields = {
message_length, request_id, response_to, opcode, -- Header
flags, full_coll_name, number_to_skip, number_to_return, query -- OP_QUERY
}
function mongodb_protocol.dissector(buffer, pinfo, tree)
length = buffer:len()
if length == 0 then return end
pinfo.cols.protocol = mongodb_protocol.name
local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")
-- Header
subtree:add_le(message_length, buffer(0,4))
subtree:add_le(request_id, buffer(4,4))
subtree:add_le(response_to, buffer(8,4))
local opcode_number = buffer(12,4):le_uint()
local opcode_name = get_opcode_name(opcode_number)
subtree:add_le(opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")
-- Payload
if opcode_name == "OP_QUERY" then
local flags_number = buffer(16,4):le_uint()
local flags_description = get_flag_description(flags_number)
subtree:add_le(flags, buffer(16,4)):append_text(" (" .. flags_description .. ")")
-- Loop over string
local string_length
for i = 20, length - 1, 1 do
if (buffer(i,1):le_uint() == 0) then
string_length = i - 20
break
end
end
subtree:add_le(full_coll_name, buffer(20,string_length))
subtree:add_le(number_to_skip, buffer(20+string_length,4))
subtree:add_le(number_to_return, buffer(24+string_length,4))
subtree:add_le(query, buffer(28+string_length,length-string_length-28))
end
end
function get_opcode_name(opcode)
local opcode_name = "Unknown"
if opcode == 1 then opcode_name = "OP_REPLY"
elseif opcode == 2001 then opcode_name = "OP_UPDATE"
elseif opcode == 2002 then opcode_name = "OP_INSERT"
elseif opcode == 2003 then opcode_name = "RESERVED"
elseif opcode == 2004 then opcode_name = "OP_QUERY"
elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
elseif opcode == 2006 then opcode_name = "OP_DELETE"
elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
elseif opcode == 2010 then opcode_name = "OP_COMMAND"
elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end
return opcode_name
end
function get_flag_description(flags)
local flags_description = "Unknown"
if flags == 0 then flags_description = "Reserved"
elseif flags == 1 then flags_description = "TailableCursor"
elseif flags == 2 then flags_description = "SlaveOk.Allow"
elseif flags == 3 then flags_description = "OplogReplay"
elseif flags == 4 then flags_description = "NoCursorTimeout"
elseif flags == 5 then flags_description = "AwaitData"
elseif flags == 6 then flags_description = "Exhaust"
elseif flags == 7 then flags_description = "Partial"
elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end
return flags_description
end
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)
OP_QUERY
的数据包详细信息最终如下所示:
解码OP_REPLY消息
我不会解码所有消息,但我们可以再看一遍。它有以下字段:
我们在这里有一个新类型:
int64
。我们不会触及文档字段,因为它变得复杂。我们制作以下字段:
response_flags =ProtoField.int32 ("mongodb.response_flags" ,"responseFlags" ,base.DEC)
cursor_id =ProtoField.int64 ("mongodb.cursor_id" ,"cursorId" ,base.DEC)
starting_from =ProtoField.int32 ("mongodb.starting_from" ,"startingFrom" ,base.DEC)
number_returned=ProtoField.int32 ("mongodb.number_returned","numberReturned",base.DEC)
documents =ProtoField.none ("mongodb.documents" ,"documents" ,base.HEX)
我们还必须将字段添加到fields
表中:
mongodb_protocol.fields = {
message_length, request_id, response_to, opcode, -- Header
flags, full_coll_name, number_to_skip, number_to_return, query, -- OP_QUERY
response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}
解析字段的方式如下:
if opcode_name == "OP_QUERY" then
...
elseif opcode_name == "OP_REPLY" then
local response_flags_number = buffer(16,4):le_uint()
local response_flags_description = get_response_flag_description(response_flags_number)
subtree:add_le(response_flags, buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
subtree:add_le(cursor_id, buffer(20,8))
subtree:add_le(starting_from, buffer(28,4))
subtree:add_le(number_returned,buffer(32,4))
subtree:add_le(documents, buffer(36,length-36))
end
这就像解析其他字段的方式一样。 cursor_id
是一个int64
,这意味着它长8个字节(8 * 8 = 64)。这意味着我们必须读取8个字节。响应标志的查找函数如下所示:
function get_response_flag_description(flags)
local flags_description = "Unknown"
if flags == 0 then flags_description = "CursorNotFound"
elseif flags == 1 then flags_description = "QueryFailure"
elseif flags == 2 then flags_description = "ShardConfigStale"
elseif flags == 3 then flags_description = "AwaitCapable"
elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end
return flags_description
end
OP_REPLY
消息最终将在数据包详细信息窗格中显示如下:
documents
字段几乎未解析。它只是作为一个字符串读取。最终代码如下所示:
mongodb_protocol = Proto("MongoDB", "MongoDB Protocol")
-- Header fields
message_length = ProtoField.int32 ("mongodb.message_length" , "messageLength" , base.DEC)
request_id = ProtoField.int32 ("mongodb.requestid" , "requestID" , base.DEC)
response_to = ProtoField.int32 ("mongodb.responseto" , "responseTo" , base.DEC)
opcode = ProtoField.int32 ("mongodb.opcode" , "opCode" , base.DEC)
-- Payload fields
flags = ProtoField.int32 ("mongodb.flags" , "flags" , base.DEC)
full_coll_name = ProtoField.string("mongodb.full_coll_name" , "fullCollectionName", base.ASCII)
number_to_skip = ProtoField.int32 ("mongodb.number_to_skip" , "numberToSkip" , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn" , base.DEC)
query = ProtoField.none ("mongodb.query" , "query" , base.HEX)
response_flags = ProtoField.int32 ("mongodb.response_flags" , "responseFlags" , base.DEC)
cursor_id = ProtoField.int64 ("mongodb.cursor_id" , "cursorId" , base.DEC)
starting_from = ProtoField.int32 ("mongodb.starting_from" , "startingFrom" , base.DEC)
number_returned = ProtoField.int32 ("mongodb.number_returned" , "numberReturned" , base.DEC)
documents = ProtoField.none ("mongodb.documents" , "documents" , base.HEX)
mongodb_protocol.fields = {
message_length, request_id, response_to, opcode, -- Header
flags, full_coll_name, number_to_skip, number_to_return, query, -- OP_QUERY
response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}
function mongodb_protocol.dissector(buffer, pinfo, tree)
length = buffer:len()
if length == 0 then return end
pinfo.cols.protocol = mongodb_protocol.name
local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")
-- Header
subtree:add_le(message_length, buffer(0,4))
subtree:add_le(request_id, buffer(4,4))
subtree:add_le(response_to, buffer(8,4))
local opcode_number = buffer(12,4):le_uint()
local opcode_name = get_opcode_name(opcode_number)
subtree:add_le(opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")
-- Payload
if opcode_name == "OP_QUERY" then
local flags_number = buffer(16,4):le_uint()
local flags_description = get_flag_description(flags_number)
subtree:add_le(flags, buffer(16,4)):append_text(" (" .. flags_description .. ")")
-- Loop over string
local string_length
for i = 20, length - 1, 1 do
if (buffer(i,1):le_uint() == 0) then
string_length = i - 20
break
end
end
subtree:add_le(full_coll_name, buffer(20,string_length))
subtree:add_le(number_to_skip, buffer(20+string_length,4))
subtree:add_le(number_to_return, buffer(24+string_length,4))
subtree:add_le(query, buffer(28+string_length,length-string_length-28))
elseif opcode_name == "OP_REPLY" then
local response_flags_number = buffer(16,4):le_uint()
local response_flags_description = get_response_flag_description(response_flags_number)
subtree:add_le(response_flags, buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
subtree:add_le(cursor_id, buffer(20,8))
subtree:add_le(starting_from, buffer(28,4))
subtree:add_le(number_returned, buffer(32,4))
subtree:add_le(documents, buffer(36,length-36))
end
end
function get_opcode_name(opcode)
local opcode_name = "Unknown"
if opcode == 1 then opcode_name = "OP_REPLY"
elseif opcode == 2001 then opcode_name = "OP_UPDATE"
elseif opcode == 2002 then opcode_name = "OP_INSERT"
elseif opcode == 2003 then opcode_name = "RESERVED"
elseif opcode == 2004 then opcode_name = "OP_QUERY"
elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
elseif opcode == 2006 then opcode_name = "OP_DELETE"
elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
elseif opcode == 2010 then opcode_name = "OP_COMMAND"
elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end
return opcode_name
end
function get_flag_description(flags)
local flags_description = "Unknown"
if flags == 0 then flags_description = "Reserved"
elseif flags == 1 then flags_description = "TailableCursor"
elseif flags == 2 then flags_description = "SlaveOk.Allow"
elseif flags == 3 then flags_description = "OplogReplay"
elseif flags == 4 then flags_description = "NoCursorTimeout"
elseif flags == 5 then flags_description = "AwaitData"
elseif flags == 6 then flags_description = "Exhaust"
elseif flags == 7 then flags_description = "Partial"
elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end
return flags_description
end
function get_response_flag_description(flags)
local flags_description = "Unknown"
if flags == 0 then flags_description = "CursorNotFound"
elseif flags == 1 then flags_description = "QueryFailure"
elseif flags == 2 then flags_description = "ShardConfigStale"
elseif flags == 3 then flags_description = "AwaitCapable"
elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end
return flags_description
end
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)
脚本中缺少其余的操作码。我不会进一步详细介绍这些内容,因为它只是重复我上面所写的内容。
mongodb_protocol = Proto("MongoDB", "MongoDB Protocol")
-- Header fields
message_length = ProtoField.int32 ("mongodb.message_length" , "messageLength" , base.DEC)
request_id = ProtoField.int32 ("mongodb.requestid" , "requestID" , base.DEC)
response_to = ProtoField.int32 ("mongodb.responseto" , "responseTo" , base.DEC)
opcode = ProtoField.int32 ("mongodb.opcode" , "opCode" , base.DEC)
-- Payload fields
flags = ProtoField.int32 ("mongodb.flags" , "flags" , base.DEC)
full_coll_name = ProtoField.string("mongodb.full_coll_name" , "fullCollectionName", base.ASCII)
number_to_skip = ProtoField.int32 ("mongodb.number_to_skip" , "numberToSkip" , base.DEC)
number_to_return= ProtoField.int32 ("mongodb.number_to_return", "numberToReturn" , base.DEC)
query = ProtoField.none ("mongodb.query" , "query" , base.HEX)
response_flags = ProtoField.int32 ("mongodb.response_flags" , "responseFlags" , base.DEC)
cursor_id = ProtoField.int64 ("mongodb.cursor_id" , "cursorId" , base.DEC)
starting_from = ProtoField.int32 ("mongodb.starting_from" , "startingFrom" , base.DEC)
number_returned = ProtoField.int32 ("mongodb.number_returned" , "numberReturned" , base.DEC)
documents = ProtoField.none ("mongodb.documents" , "documents" , base.HEX)
mongodb_protocol.fields = {
message_length, request_id, response_to, opcode, -- Header
flags, full_coll_name, number_to_skip, number_to_return, query, -- OP_QUERY
response_flags, cursor_id, starting_from, number_returned, documents -- OP_REPLY
}
function mongodb_protocol.dissector(buffer, pinfo, tree)
length = buffer:len()
if length == 0 then return end
pinfo.cols.protocol = mongodb_protocol.name
local subtree = tree:add(mongodb_protocol, buffer(), "MongoDB Protocol Data")
-- Header
subtree:add_le(message_length, buffer(0,4))
subtree:add_le(request_id, buffer(4,4))
subtree:add_le(response_to, buffer(8,4))
local opcode_number = buffer(12,4):le_uint()
local opcode_name = get_opcode_name(opcode_number)
subtree:add_le(opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")
-- Payload
if opcode_name == "OP_QUERY" then
local flags_number = buffer(16,4):le_uint()
local flags_description = get_flag_description(flags_number)
subtree:add_le(flags, buffer(16,4)):append_text(" (" .. flags_description .. ")")
-- Loop over string
local string_length
for i = 20, length - 1, 1 do
if (buffer(i,1):le_uint() == 0) then
string_length = i - 20
break
end
end
subtree:add_le(full_coll_name, buffer(20,string_length))
subtree:add_le(number_to_skip, buffer(20+string_length,4))
subtree:add_le(number_to_return, buffer(24+string_length,4))
subtree:add_le(query, buffer(28+string_length,length-string_length-28))
elseif opcode_name == "OP_REPLY" then
local response_flags_number = buffer(16,4):le_uint()
local response_flags_description = get_response_flag_description(response_flags_number)
subtree:add_le(response_flags, buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
subtree:add_le(cursor_id, buffer(20,8))
subtree:add_le(starting_from, buffer(28,4))
subtree:add_le(number_returned, buffer(32,4))
subtree:add_le(documents, buffer(36,length-36))
end
end
function get_opcode_name(opcode)
local opcode_name = "Unknown"
if opcode == 1 then opcode_name = "OP_REPLY"
elseif opcode == 2001 then opcode_name = "OP_UPDATE"
elseif opcode == 2002 then opcode_name = "OP_INSERT"
elseif opcode == 2003 then opcode_name = "RESERVED"
elseif opcode == 2004 then opcode_name = "OP_QUERY"
elseif opcode == 2005 then opcode_name = "OP_GET_MORE"
elseif opcode == 2006 then opcode_name = "OP_DELETE"
elseif opcode == 2007 then opcode_name = "OP_KILL_CURSORS"
elseif opcode == 2010 then opcode_name = "OP_COMMAND"
elseif opcode == 2011 then opcode_name = "OP_COMMANDREPLY" end
return opcode_name
end
function get_flag_description(flags)
local flags_description = "Unknown"
if flags == 0 then flags_description = "Reserved"
elseif flags == 1 then flags_description = "TailableCursor"
elseif flags == 2 then flags_description = "SlaveOk.Allow"
elseif flags == 3 then flags_description = "OplogReplay"
elseif flags == 4 then flags_description = "NoCursorTimeout"
elseif flags == 5 then flags_description = "AwaitData"
elseif flags == 6 then flags_description = "Exhaust"
elseif flags == 7 then flags_description = "Partial"
elseif 8 <= flags and flags <= 31 then flags_description = "Reserved" end
return flags_description
end
function get_response_flag_description(flags)
local flags_description = "Unknown"
if flags == 0 then flags_description = "CursorNotFound"
elseif flags == 1 then flags_description = "QueryFailure"
elseif flags == 2 then flags_description = "ShardConfigStale"
elseif flags == 3 then flags_description = "AwaitCapable"
elseif 4 <= flags and flags <= 31 then flags_description = "Reserved" end
return flags_description
end
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)