在这里,我将解释如何将代码分成几个模块。在我的例子中,我将标题和有效负载部分分成单独的文件。
划分代码
我想为Header
提供单独的文件,OP_REPLY
消息的文件,OP_QUERY
消息的文件,帮助函数的文件和main文件。最终的文件结构如下所示:
我创建一个名为mongodb的独立文件夹。这将包含协议的所有模块。主文件仍然直接存在于plugins中。
加载文件
在Lua中,我们通常有两个可以加载文件的函数:dofile
和require
。我将使用require
,原因如下
Lua提供了一个更高级的函数来加载和运行库,称为require
。粗略地说,require
与dofile
完成相同的工作,但有两个重要的区别。首先,require
在路径中搜索文件;第二,require
控制是否已运行文件以避免重复工作。由于这些功能,require
是Lua中用于加载库的首选功能。
除了require
我们必须使用package.prepend_path()
。package.path
是Wireshark查找文件的地方。 prepend_path
将为package.path
添加一个新路径。在我的特定情况下,工作目录是Wireshark根目录,这意味着我必须将“plugins / mongodb”添加到package.path。如果您使用的是另一个操作系统,那么您可能有另一条路径,或者将文件放在另一个文件夹中(例如用户插件目录而不是全局插件目录)。
要导入模块,我们必须在主文件的开头添加以下内容:
package.prepend_path("plugins/mongodb")
local header = require("header")
如前所述,prepend_path()
行将使Wireshark能够在plugins / mongodb目录中查找文件,而require行将导入header.lua
中的代码。不应包含文件结尾。正如我们进一步看到的那样,我在header.lua中“导出”一个Table(对象),我们可以在主文件中使用点表示法:local var = header.myFunction()
。
创建header.lua
我正在将一些与头文件相关的代码从主文件移到header.lua文件中:
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
local m = {}
function m.parse(headerSubtree, buffer, message_length, request_id, response_to, opcode)
headerSubtree:add_le(message_length, buffer(0,4))
headerSubtree:add_le(request_id, buffer(4,4))
headerSubtree:add_le(response_to, buffer(8,4))
local opcode_number = buffer(12,4):le_uint()
local opcode_name = get_opcode_name(opcode_number)
headerSubtree:add_le(opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")
return opcode_name
end
return m
我已经将mongodb.lua
中的get_opcode_name()
移动到header.lua。我还创建了一个名为m的Table(对象),我创建了一个名为parse()
的新函数。 parse()
函数包含之前在主文件中的头字段解析逻辑。因为headerSubtree
是一个引用类型,所以我不必从函数中返回它:在从parse()
返回后它仍然会被修改。但是,我需要在主文件中使用opcode_name
,所以我会返回它。表m从文件返回,因此可以在主文件中使用。我们不必将get_opcode_name()添加到m,因为它仅在header.lua中使用。
在取出一些Header内容后,主文件看起来像这样:
package.prepend_path("plugins/mongodb")
local header = require("header")
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")
local headerSubtree = subtree:add(mongodb_protocol, buffer(), "Header")
local payloadSubtree = subtree:add(mongodb_protocol, buffer(), "Payload")
-- Header
local opcode_name = header.parse(headerSubtree, buffer, message_length, request_id, response_to, opcode)
-- Payload
if opcode_name == "OP_QUERY" then
local flags_number = buffer(16,4):le_uint()
local flags_description = get_flag_description(flags_number)
payloadSubtree: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
payloadSubtree:add_le(full_coll_name, buffer(20,string_length))
payloadSubtree:add_le(number_to_skip, buffer(20+string_length,4))
payloadSubtree:add_le(number_to_return, buffer(24+string_length,4))
payloadSubtree: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)
payloadSubtree:add_le(response_flags, buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
payloadSubtree:add_le(cursor_id, buffer(20,8))
payloadSubtree:add_le(starting_from, buffer(28,4))
payloadSubtree:add_le(number_returned, buffer(32,4))
payloadSubtree:add_le(documents, buffer(36,length-36))
end
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)
注意对header.parse()
的调用。如上所述,我返回opcode_name
,因为我需要在代码中进一步向下。子树headerSubtree
也将被修改(添加到它的字段),因为它是一个引用类型,因此在parse()
中是可变的。
创建OP_QUERY.lua 和 OP_REPLY.lua
如您所见,主文件中仍有很多Header
内容可以移动到Header模块中。稍后我会移动它,但首先我要对OP_QUERY
和OP_REPLY
解析代码执行相同的操作,就像使用Header代码一样
我正在制作OP_QUERY.lua并将get_flag_description()
和OP_QUERY
解析逻辑移动过去:
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 m = {}
function m.parse(payloadSubtree, buffer, length, flags, full_coll_name, number_to_skip, number_to_return, query)
local flags_number = buffer(16,4):le_uint()
local flags_description = get_flag_description(flags_number)
payloadSubtree: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
payloadSubtree:add_le(full_coll_name, buffer(20,string_length))
payloadSubtree:add_le(number_to_skip, buffer(20+string_length,4))
payloadSubtree:add_le(number_to_return, buffer(24+string_length,4))
payloadSubtree:add_le(query, buffer(28+string_length,length-string_length-28))
end
return m
我也在制作OP_REPLY.lua并将get_response_flag_description()
和OP_REPLY
解析逻辑移动过去:
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 m = {}
function m.parse(payloadSubtree, buffer, response_flags, cursor_id, starting_from, number_returned, documents)
local response_flags_number = buffer(16,4):le_uint()
local response_flags_description = get_response_flag_description(response_flags_number)
payloadSubtree:add_le(response_flags, buffer(16,4)):append_text(" (" .. response_flags_description .. ")")
payloadSubtree:add_le(cursor_id, buffer(20,8))
payloadSubtree:add_le(starting_from, buffer(28,4))
payloadSubtree:add_le(number_returned, buffer(32,4))
payloadSubtree:add_le(documents, buffer(36,length-36))
end
return m
主文件如下:
package.prepend_path("plugins/mongodb")
local header = require("header")
local op_query = require("OP_QUERY")
local op_reply = require("OP_REPLY")
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")
local headerSubtree = subtree:add(mongodb_protocol, buffer(), "Header")
local payloadSubtree = subtree:add(mongodb_protocol, buffer(), "Payload")
-- Header
local opcode_name = header.parse(headerSubtree, buffer, message_length, request_id, response_to, opcode)
-- Payload
if opcode_name == "OP_QUERY" then
op_query.parse(payloadSubtree, buffer, length, flags, full_coll_name, number_to_skip, number_to_return, query)
elseif opcode_name == "OP_REPLY" then
op_reply.parse(payloadSubtree, buffer, response_flags, cursor_id, starting_from, number_returned, documents)
end
end
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)
当然,我们必须require
这两个新文件。
移动字段创建代码
主文件mongodb.lua现在看起来更干净了。仍有Header
,OP_QUERY
和OP_REPLY
相关逻辑可以移动到各自的文件中。移动字段创建代码后,我们也可以摆脱对三个parse()
方法的笨拙调用。它们包含的参数太多,应该从模块中了解到。
让我们在模块中移动字段创建代码。这是header.lua:
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
local m = {
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)
}
function m.get_fields()
local fields = {
message_length = m.message_length,
request_id = m.request_id,
response_to = m.response_to,
opcode = m.opcode
}
return fields
end
function m.parse(headerSubtree, buffer)
headerSubtree:add_le(m.message_length, buffer(0,4))
headerSubtree:add_le(m.request_id, buffer(4,4))
headerSubtree:add_le(m.response_to, buffer(8,4))
local opcode_number = buffer(12,4):le_uint()
local opcode_name = get_opcode_name(opcode_number)
headerSubtree:add_le(m.opcode, buffer(12,4)):append_text(" (" .. opcode_name .. ")")
return opcode_name
end
return m
这些字段作为m
表的成员存在。 get_fields()
函数用于在模块外部访问它们。另请注意,parse()
函数通过模块本身访问字段,而不是将它们作为参数传递。
我还将OP_QUERY
和OP_REPLY
的字段移动到各自的模块中。主文件现在看起来像这样:
package.prepend_path("plugins/mongodb")
local helpers = require("helpers")
local header = require("header")
local op_query = require("OP_QUERY")
local op_reply = require("OP_REPLY")
mongodb_protocol = Proto("MongoDB", "MongoDB Protocol")
local header_fields = header.get_fields()
local op_query_fields = op_query.get_fields()
local op_reply_fields = op_reply.get_fields()
helpers.merge_tables( header_fields, mongodb_protocol.fields)
helpers.merge_tables(op_query_fields, mongodb_protocol.fields)
helpers.merge_tables(op_reply_fields, mongodb_protocol.fields)
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")
local headerSubtree = subtree:add(mongodb_protocol, buffer(), "Header")
local payloadSubtree = subtree:add(mongodb_protocol, buffer(), "Payload")
local opcode_name = header.parse(headerSubtree, buffer)
if opcode_name == "OP_QUERY" then op_query.parse(payloadSubtree, buffer, length)
elseif opcode_name == "OP_REPLY" then op_reply.parse(payloadSubtree, buffer) end
end
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(59274, mongodb_protocol)
您可以看到所有字段初始化代码都消失了。我们仍然需要在mongodb_protocol.fields中插入字段,这就是我们用get_fields()获取它们的原因。我使用一个名为merge_tables()的辅助函数将三个表合并在一起。我把这个函数放在一个名为helpers.lua的模块中。它看起来像这样:
local m = {}
-- Made by Doug Currie (https://stackoverflow.com/users/33252/doug-currie)
-- on Stack Overflow. https://stackoverflow.com/questions/1283388/lua-merge-tables
function m.merge_tables(from, to)
for k,v in pairs(from) do to[k] = v end
end
return m
如您所见,我在Stack Overflow上找到了代码。
最后,您可以看到对parse()的调用已缩短为:
local opcode_name = header.parse(headerSubtree, buffer)
if opcode_name == "OP_QUERY" then op_query.parse(payloadSubtree, buffer, length)
elseif opcode_name == "OP_REPLY" then op_reply.parse(payloadSubtree, buffer) end
我们不必再传递所有字段变量,因为它们已经被放入模块本身。
完整实现
因此,您可以将代码分成几个文件。
- 使用package.prepend_path()将目录添加到包路径。
- 使用require()从另一个文件中读取代码。
- 在模块文件中创建一个名为m的表(对象)或任何你想要的。
- 向表中添加方法和变量。
- 返回您创建的表并在主文件中使用它。